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/src/terminals/model.ts
CHANGED
|
@@ -10,101 +10,76 @@ import type { Title, Widget } from '@lumino/widgets';
|
|
|
10
10
|
import type { IAgentSessions } from '../agentSessions';
|
|
11
11
|
import { fetchRunningAgents } from './detection';
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* The widget shape every entry in `ITerminalTracker` takes — kept here
|
|
15
|
-
* so the registry's call sites read cleanly.
|
|
16
|
-
*/
|
|
17
13
|
export type TerminalWidget = MainAreaWidget<ITerminal.ITerminal>;
|
|
18
14
|
|
|
19
15
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* buffer without taking a dependency on `@xterm/xterm` — the same access the
|
|
24
|
-
* terminal-notifications plugin already relies on. If the field is ever
|
|
25
|
-
* renamed, `_term` reads back `undefined` and the activity line is simply
|
|
26
|
-
* omitted: the row falls back to its title alone.
|
|
16
|
+
* Slice of xterm.js reached through the terminal widget's private `_term`
|
|
17
|
+
* field, so the buffer is readable without depending on `@xterm/xterm`.
|
|
18
|
+
* If upstream renames the field, the activity line is simply omitted.
|
|
27
19
|
*/
|
|
28
20
|
interface IXtermBufferLine {
|
|
21
|
+
/**
|
|
22
|
+
* Whether this row is a soft-wrap continuation of the previous row.
|
|
23
|
+
*/
|
|
29
24
|
readonly isWrapped: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Translate the row to its text, trimming trailing whitespace on request.
|
|
27
|
+
*/
|
|
30
28
|
translateToString(trimRight?: boolean): string;
|
|
31
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* An xterm.js buffer exposing the cursor position and row access.
|
|
32
|
+
*/
|
|
32
33
|
interface IXtermBuffer {
|
|
34
|
+
/**
|
|
35
|
+
* Buffer row of the viewport's top line when scrolled to the bottom.
|
|
36
|
+
*/
|
|
33
37
|
readonly baseY: number;
|
|
38
|
+
/**
|
|
39
|
+
* The cursor's row relative to the viewport top.
|
|
40
|
+
*/
|
|
34
41
|
readonly cursorY: number;
|
|
42
|
+
/**
|
|
43
|
+
* Get the line at the given buffer row, or `undefined` if out of range.
|
|
44
|
+
*/
|
|
35
45
|
getLine(index: number): IXtermBufferLine | undefined;
|
|
36
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* An xterm.js terminal narrowed to its buffer namespace.
|
|
49
|
+
*/
|
|
37
50
|
interface IXtermTerminal {
|
|
51
|
+
/**
|
|
52
|
+
* The buffer namespace; `active` is the buffer currently on screen.
|
|
53
|
+
*/
|
|
38
54
|
readonly buffer: { readonly active: IXtermBuffer };
|
|
39
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* The terminal widget content with its private xterm.js instance exposed.
|
|
58
|
+
*/
|
|
40
59
|
interface ITerminalContentInternals extends ITerminal.ITerminal {
|
|
60
|
+
/**
|
|
61
|
+
* The underlying xterm.js terminal; absent until the widget creates it.
|
|
62
|
+
*/
|
|
41
63
|
_term?: IXtermTerminal;
|
|
42
64
|
}
|
|
43
65
|
|
|
44
|
-
/**
|
|
45
|
-
* How often to ask the server which agent (if any) is running in each
|
|
46
|
-
* terminal. Snappy enough that a manually-started agent's logo shows up
|
|
47
|
-
* within a few seconds, light enough that walking the shells' process trees
|
|
48
|
-
* stays negligible.
|
|
49
|
-
*/
|
|
50
66
|
const DETECT_POLL_INTERVAL_MS = 3000;
|
|
51
67
|
|
|
52
|
-
/**
|
|
53
|
-
* Upper bound on the exponential backoff when detection fails repeatedly
|
|
54
|
-
* (e.g. the endpoint is missing on an older server). Matches the rest of the
|
|
55
|
-
* plugin's polls.
|
|
56
|
-
*/
|
|
57
68
|
const DETECT_POLL_MAX_MS = 300_000;
|
|
58
69
|
|
|
59
70
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* issuing an agent's command and its process actually being spawnable, so the
|
|
63
|
-
* freshly-launched logo doesn't blink off if a detection poll lands in that
|
|
64
|
-
* sliver. Comfortably longer than a cold agent start; short enough that a tag
|
|
65
|
-
* for an agent that never really started clears quickly.
|
|
71
|
+
* Window during which a fresh launch tag outranks a `null` detection result:
|
|
72
|
+
* covers the gap between launching an agent and its process becoming visible.
|
|
66
73
|
*/
|
|
67
74
|
const LAUNCH_GRACE_MS = 4000;
|
|
68
75
|
|
|
69
|
-
/**
|
|
70
|
-
* How often to re-read each open coding-agent terminal's output buffer to
|
|
71
|
-
* refresh the "latest activity" line shown under its row. Brisk enough to feel
|
|
72
|
-
* live while an agent works, cheap because it only walks the last screenful of
|
|
73
|
-
* a handful of terminals and emits only when a line actually changes.
|
|
74
|
-
*/
|
|
75
76
|
const ACTIVITY_POLL_INTERVAL_MS = 1500;
|
|
76
77
|
|
|
77
78
|
/**
|
|
78
|
-
* Source-of-truth model for the running-terminals panel
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* session continues running on the backend.
|
|
83
|
-
*
|
|
84
|
-
* For sessions running a coding agent it also surfaces a *latest activity*
|
|
85
|
-
* line — the freshest meaningful line of the terminal's output, read from the
|
|
86
|
-
* open tab's xterm buffer on a poll (see {@link activityFor}) — so each row
|
|
87
|
-
* shows what its agent is doing, not just its name.
|
|
88
|
-
*
|
|
89
|
-
* It also resolves *which agent is running* in each session, so the panel can
|
|
90
|
-
* badge rows with the agent's logo. Two inputs feed that, reconciled by
|
|
91
|
-
* {@link agentCommandFor}:
|
|
92
|
-
* - an optimistic launch tag ({@link IAgentSessions}) written when we start
|
|
93
|
-
* an agent ourselves — instant, but blind to the agent later exiting; and
|
|
94
|
-
* - authoritative server-side process detection, polled here, which works
|
|
95
|
-
* for hand-started agents too and clears once an agent exits.
|
|
96
|
-
*
|
|
97
|
-
* Upstream session signals:
|
|
98
|
-
* - `serviceManager.terminals.runningChanged` is the authoritative
|
|
99
|
-
* list of live sessions; everything not in it has been shut down
|
|
100
|
-
* server-side and we must drop it.
|
|
101
|
-
* - `tracker.widgetAdded` plus the per-widget `title.changed` /
|
|
102
|
-
* `disposed` signals keep our label cache in sync with whatever
|
|
103
|
-
* xterm/the launcher has set on the open tabs.
|
|
104
|
-
*
|
|
105
|
-
* `stateChanged` is emitted for every kind of update; the panel hooks it
|
|
106
|
-
* through a `UseSignal` so a single subscription re-renders the list on
|
|
107
|
-
* any change.
|
|
79
|
+
* Source-of-truth model for the running-terminals panel: live sessions,
|
|
80
|
+
* labels cached across tab closes, agent badges (launch tags reconciled with
|
|
81
|
+
* polled detection, see {@link agentCommandFor}), and per-row latest-activity
|
|
82
|
+
* lines. `stateChanged` fires on every kind of update.
|
|
108
83
|
*/
|
|
109
84
|
export class SessionRegistry implements IDisposable {
|
|
110
85
|
constructor(options: SessionRegistry.IOptions) {
|
|
@@ -120,18 +95,9 @@ export class SessionRegistry implements IDisposable {
|
|
|
120
95
|
this._tracker.forEach(widget => this._trackWidget(widget));
|
|
121
96
|
this._refreshLive();
|
|
122
97
|
|
|
123
|
-
// Track which terminal is the current widget in the main area so the panel
|
|
124
|
-
// can highlight its row — and highlight nothing while a notebook or any
|
|
125
|
-
// other non-terminal tab is current instead. `currentChanged` is optional
|
|
126
|
-
// on the shell interface (not every shell can switch focus), so guard it;
|
|
127
|
-
// when it is absent the highlight stays off.
|
|
128
98
|
this._shell?.currentChanged?.connect(this._onShellCurrentChanged, this);
|
|
129
99
|
this._updateCurrent();
|
|
130
100
|
|
|
131
|
-
// A freshly written launch tag should re-render the panel immediately
|
|
132
|
-
// (show the logo) and again once the grace window closes (so a tag for
|
|
133
|
-
// an agent that never started, or that detection later contradicts,
|
|
134
|
-
// doesn't linger).
|
|
135
101
|
this._agentSessions?.changed.connect(this._onTagChanged, this);
|
|
136
102
|
|
|
137
103
|
this._poll = new Poll({
|
|
@@ -145,10 +111,8 @@ export class SessionRegistry implements IDisposable {
|
|
|
145
111
|
standby: 'when-hidden'
|
|
146
112
|
});
|
|
147
113
|
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// separate from the detection poll so reading xterm buffers neither delays
|
|
151
|
-
// nor is delayed by the server round-trip that drives the agent badges.
|
|
114
|
+
// Separate from the detection poll so reading xterm buffers neither
|
|
115
|
+
// delays nor is delayed by the server round-trip.
|
|
152
116
|
this._activityPoll = new Poll({
|
|
153
117
|
name: '@xtralab/terminals:activity',
|
|
154
118
|
factory: () => this._refreshActivity(),
|
|
@@ -158,21 +122,21 @@ export class SessionRegistry implements IDisposable {
|
|
|
158
122
|
}
|
|
159
123
|
|
|
160
124
|
/**
|
|
161
|
-
* Emitted
|
|
162
|
-
* running agent changes.
|
|
125
|
+
* Emitted on any change to the session list, labels, badges, or activity.
|
|
163
126
|
*/
|
|
164
127
|
get stateChanged(): ISignal<this, void> {
|
|
165
128
|
return this._stateChanged;
|
|
166
129
|
}
|
|
167
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Whether the registry has been disposed.
|
|
133
|
+
*/
|
|
168
134
|
get isDisposed(): boolean {
|
|
169
135
|
return this._isDisposed;
|
|
170
136
|
}
|
|
171
137
|
|
|
172
138
|
/**
|
|
173
|
-
* Names of all live sessions
|
|
174
|
-
* rendered list keeps a consistent order (roughly creation order) as
|
|
175
|
-
* new sessions are added and old ones shut down.
|
|
139
|
+
* Names of all live sessions in stable (roughly creation) order.
|
|
176
140
|
*/
|
|
177
141
|
sessionNames(): string[] {
|
|
178
142
|
const names = Array.from(this._live);
|
|
@@ -181,23 +145,17 @@ export class SessionRegistry implements IDisposable {
|
|
|
181
145
|
}
|
|
182
146
|
|
|
183
147
|
/**
|
|
184
|
-
* True iff the named session is still on the server.
|
|
185
|
-
* to skip a row during the brief window between the session's shutdown
|
|
186
|
-
* and the next `runningChanged` arriving.
|
|
148
|
+
* True iff the named session is still on the server.
|
|
187
149
|
*/
|
|
188
150
|
has(name: string): boolean {
|
|
189
151
|
return this._live.has(name);
|
|
190
152
|
}
|
|
191
153
|
|
|
192
154
|
/**
|
|
193
|
-
* Display name for the session. The cache wins
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* during reconnect from flickering into the panel. The live widget
|
|
198
|
-
* title is used when no cache exists, and the `Terminal {name}`
|
|
199
|
-
* fallback covers sessions we have never seen a widget for (e.g.
|
|
200
|
-
* surviving a lab reload).
|
|
155
|
+
* Display name for the session. The cache wins — it holds only "real"
|
|
156
|
+
* labels (see `_cacheLabel`), so the transient `Terminal {name}` an xterm
|
|
157
|
+
* widget shows during reconnect never flickers into the panel; the final
|
|
158
|
+
* fallback covers sessions never seen with a widget (e.g. a lab reload).
|
|
201
159
|
*/
|
|
202
160
|
labelFor(name: string): string {
|
|
203
161
|
const cached = this._labels.get(name);
|
|
@@ -212,18 +170,10 @@ export class SessionRegistry implements IDisposable {
|
|
|
212
170
|
}
|
|
213
171
|
|
|
214
172
|
/**
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* Server-side detection is authoritative whenever it reports a running
|
|
221
|
-
* agent. A launch tag fills two gaps: the startup grace window right after
|
|
222
|
-
* we launch an agent (so its logo shows before its process is detectable),
|
|
223
|
-
* and any time detection is unavailable (older server, transient error).
|
|
224
|
-
* Once the grace window has passed and detection has reported the session
|
|
225
|
-
* as idle, the tag is ignored — that is how the badge clears when an agent
|
|
226
|
-
* exits.
|
|
173
|
+
* Agent identifier for the session (configured command or canonical id), or
|
|
174
|
+
* `null`. Detection wins when it reports an agent; a launch tag fills the
|
|
175
|
+
* grace window and detection outages, and is ignored once detection says
|
|
176
|
+
* idle — that is how the badge clears when an agent exits.
|
|
227
177
|
*/
|
|
228
178
|
agentCommandFor(name: string): string | null {
|
|
229
179
|
// `string` = detected running agent, `null` = polled and idle,
|
|
@@ -246,13 +196,9 @@ export class SessionRegistry implements IDisposable {
|
|
|
246
196
|
}
|
|
247
197
|
|
|
248
198
|
/**
|
|
249
|
-
* The agent command
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
* {@link agentCommandFor} this never falls back to the optimistic launch
|
|
253
|
-
* tags: callers about to *write into* a session use it, and text must only
|
|
254
|
-
* ever be sent to a confirmed running agent — pasted into a shell prompt it
|
|
255
|
-
* would be executed.
|
|
199
|
+
* The agent command detection currently confirms for the session, or
|
|
200
|
+
* `null`. Never falls back to launch tags: callers about to *write into* a
|
|
201
|
+
* session use it, and text pasted into a shell prompt would be executed.
|
|
256
202
|
*/
|
|
257
203
|
detectedCommandFor(name: string): string | null {
|
|
258
204
|
const detected = this._detected.get(name);
|
|
@@ -260,13 +206,9 @@ export class SessionRegistry implements IDisposable {
|
|
|
260
206
|
}
|
|
261
207
|
|
|
262
208
|
/**
|
|
263
|
-
* The most recent meaningful line of
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
* buffer holds only the agent's input box and chrome. Refreshed on a poll by
|
|
267
|
-
* {@link _refreshActivity}; shown as a smaller line under the row's title.
|
|
268
|
-
* Always `null` while the activity line is disabled (see
|
|
269
|
-
* {@link setActivityEnabled}).
|
|
209
|
+
* The most recent meaningful line of the session's output, or `null` when
|
|
210
|
+
* nothing qualifies (no running agent, closed tab, chrome-only buffer, or
|
|
211
|
+
* the activity line is disabled).
|
|
270
212
|
*/
|
|
271
213
|
activityFor(name: string): string | null {
|
|
272
214
|
if (!this._activityEnabled) {
|
|
@@ -276,11 +218,8 @@ export class SessionRegistry implements IDisposable {
|
|
|
276
218
|
}
|
|
277
219
|
|
|
278
220
|
/**
|
|
279
|
-
* Turn the per-row
|
|
280
|
-
*
|
|
281
|
-
* activity poll stops reading terminal buffers and any cached lines are
|
|
282
|
-
* dropped, so rows fall back to their title alone; when turned back on the
|
|
283
|
-
* lines are repopulated on the spot. A no-op when the value is unchanged.
|
|
221
|
+
* Turn the per-row activity line on or off (the `showAgentActivity`
|
|
222
|
+
* setting). Off drops cached lines; on repopulates them on the spot.
|
|
284
223
|
*/
|
|
285
224
|
setActivityEnabled(enabled: boolean): void {
|
|
286
225
|
if (enabled === this._activityEnabled) {
|
|
@@ -288,8 +227,6 @@ export class SessionRegistry implements IDisposable {
|
|
|
288
227
|
}
|
|
289
228
|
this._activityEnabled = enabled;
|
|
290
229
|
if (enabled) {
|
|
291
|
-
// Repopulate immediately rather than waiting for the next poll tick;
|
|
292
|
-
// `_refreshActivity` emits `stateChanged` if it finds anything to show.
|
|
293
230
|
void this._refreshActivity();
|
|
294
231
|
} else {
|
|
295
232
|
this._activity.clear();
|
|
@@ -298,9 +235,7 @@ export class SessionRegistry implements IDisposable {
|
|
|
298
235
|
}
|
|
299
236
|
|
|
300
237
|
/**
|
|
301
|
-
*
|
|
302
|
-
* switch behavior between "activate existing tab" and "open a new tab
|
|
303
|
-
* connected to the live session".
|
|
238
|
+
* The open widget for a session, if any.
|
|
304
239
|
*/
|
|
305
240
|
widgetFor(name: string): TerminalWidget | null {
|
|
306
241
|
return (
|
|
@@ -309,22 +244,16 @@ export class SessionRegistry implements IDisposable {
|
|
|
309
244
|
}
|
|
310
245
|
|
|
311
246
|
/**
|
|
312
|
-
* Session name of the terminal that is
|
|
313
|
-
*
|
|
314
|
-
* example a notebook or text editor is current). The panel uses this to
|
|
315
|
-
* highlight the current terminal's row — mirroring how the file browser
|
|
316
|
-
* surfaces the open document — and to leave every row unhighlighted while a
|
|
317
|
-
* non-terminal tab is current.
|
|
247
|
+
* Session name of the terminal that is the shell's current main-area
|
|
248
|
+
* widget, or `null` when the current widget is not a terminal.
|
|
318
249
|
*/
|
|
319
250
|
currentSessionName(): string | null {
|
|
320
251
|
return this._currentName;
|
|
321
252
|
}
|
|
322
253
|
|
|
323
254
|
/**
|
|
324
|
-
* Stable per-session rank
|
|
325
|
-
*
|
|
326
|
-
* sessions come and go. Cleaned up when the session is shut down so the
|
|
327
|
-
* counter does not grow without bound across long sessions.
|
|
255
|
+
* Stable per-session rank in observation order, so rows don't reshuffle as
|
|
256
|
+
* sessions come and go.
|
|
328
257
|
*/
|
|
329
258
|
rankFor(name: string): number {
|
|
330
259
|
let rank = this._ranks.get(name);
|
|
@@ -335,6 +264,9 @@ export class SessionRegistry implements IDisposable {
|
|
|
335
264
|
return rank;
|
|
336
265
|
}
|
|
337
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Dispose of the registry's polls and signal connections.
|
|
269
|
+
*/
|
|
338
270
|
dispose(): void {
|
|
339
271
|
if (this._isDisposed) {
|
|
340
272
|
return;
|
|
@@ -364,16 +296,12 @@ export class SessionRegistry implements IDisposable {
|
|
|
364
296
|
}
|
|
365
297
|
|
|
366
298
|
/**
|
|
367
|
-
* Adopt `next` as the live set and prune every per-session map
|
|
368
|
-
*
|
|
369
|
-
* detection results, and the shared launch tag. Without this the maps (and
|
|
370
|
-
* the rank counter) would grow without bound as terminals come and go.
|
|
299
|
+
* Adopt `next` as the live set and prune every per-session map, so the
|
|
300
|
+
* maps and rank counter don't grow without bound.
|
|
371
301
|
*/
|
|
372
302
|
private _reconcileLive(next: Set<string>): void {
|
|
373
|
-
//
|
|
374
|
-
//
|
|
375
|
-
// not enumerable, so we drive the prune from the sessions we know about;
|
|
376
|
-
// `_firstSeen` has an entry for every session that has ever been live.)
|
|
303
|
+
// The tag map is not enumerable, so the tag prune below is driven from
|
|
304
|
+
// `_firstSeen`, which has an entry for every session ever live.
|
|
377
305
|
const known = Array.from(this._firstSeen.keys());
|
|
378
306
|
|
|
379
307
|
for (const name of next) {
|
|
@@ -407,10 +335,8 @@ export class SessionRegistry implements IDisposable {
|
|
|
407
335
|
this._activity.delete(name);
|
|
408
336
|
}
|
|
409
337
|
}
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
// a stale tag could otherwise mislabel a brand-new terminal that happens
|
|
413
|
-
// to reuse a closed session's name.
|
|
338
|
+
// terminado reuses session names, so a stale tag could mislabel a
|
|
339
|
+
// brand-new terminal that reuses a closed session's name.
|
|
414
340
|
for (const name of known) {
|
|
415
341
|
if (!next.has(name)) {
|
|
416
342
|
this._agentSessions?.delete(name);
|
|
@@ -419,9 +345,8 @@ export class SessionRegistry implements IDisposable {
|
|
|
419
345
|
}
|
|
420
346
|
|
|
421
347
|
/**
|
|
422
|
-
* Poll body:
|
|
423
|
-
*
|
|
424
|
-
* clearing them, so a transient error doesn't strip every badge.
|
|
348
|
+
* Poll body: update the detection map from the server. On failure the
|
|
349
|
+
* previous results are kept, so a transient error doesn't strip every badge.
|
|
425
350
|
*/
|
|
426
351
|
private async _refreshDetection(): Promise<void> {
|
|
427
352
|
const commands = this._detectCommands();
|
|
@@ -449,15 +374,9 @@ export class SessionRegistry implements IDisposable {
|
|
|
449
374
|
}
|
|
450
375
|
|
|
451
376
|
/**
|
|
452
|
-
* Poll body:
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
* buffer, editors run full-screen UIs that are not "activity", and plain
|
|
456
|
-
* shells would only echo their prompt. While a tab is reopening (its xterm
|
|
457
|
-
* is not ready yet) any existing line is kept; once the buffer is readable
|
|
458
|
-
* but has nothing worth showing the line is dropped, so a screen the agent
|
|
459
|
-
* has cleared doesn't leave a frozen line behind. Cached lines for sessions
|
|
460
|
-
* that no longer qualify are pruned. Emits only when something changed.
|
|
377
|
+
* Poll body: cache the latest meaningful output line of each open agent
|
|
378
|
+
* terminal (closed tabs have no live buffer; editors and shells don't
|
|
379
|
+
* qualify). Emits only when something changed.
|
|
461
380
|
*/
|
|
462
381
|
private async _refreshActivity(): Promise<void> {
|
|
463
382
|
if (!this._activityEnabled) {
|
|
@@ -477,8 +396,8 @@ export class SessionRegistry implements IDisposable {
|
|
|
477
396
|
qualifying.add(name);
|
|
478
397
|
const term = (widget.content as ITerminalContentInternals)._term;
|
|
479
398
|
if (!term) {
|
|
480
|
-
// The tab is reopening
|
|
481
|
-
//
|
|
399
|
+
// The tab is reopening; keep any existing line until its xterm buffer
|
|
400
|
+
// is readable again.
|
|
482
401
|
continue;
|
|
483
402
|
}
|
|
484
403
|
const line = Private.readActivity(term);
|
|
@@ -488,8 +407,8 @@ export class SessionRegistry implements IDisposable {
|
|
|
488
407
|
changed = true;
|
|
489
408
|
}
|
|
490
409
|
} else if (this._activity.delete(name)) {
|
|
491
|
-
// Readable buffer with nothing to surface (e.g.
|
|
492
|
-
//
|
|
410
|
+
// Readable buffer with nothing to surface (e.g. a cleared screen) —
|
|
411
|
+
// drop the stale line instead of freezing it.
|
|
493
412
|
changed = true;
|
|
494
413
|
}
|
|
495
414
|
}
|
|
@@ -506,8 +425,8 @@ export class SessionRegistry implements IDisposable {
|
|
|
506
425
|
|
|
507
426
|
private _onTagChanged(): void {
|
|
508
427
|
this._stateChanged.emit();
|
|
509
|
-
// Re-
|
|
510
|
-
//
|
|
428
|
+
// Re-emit once the grace window closes so a tag detection never confirmed
|
|
429
|
+
// (or has since contradicted) stops being shown.
|
|
511
430
|
setTimeout(() => {
|
|
512
431
|
if (!this._isDisposed) {
|
|
513
432
|
this._stateChanged.emit();
|
|
@@ -525,10 +444,8 @@ export class SessionRegistry implements IDisposable {
|
|
|
525
444
|
}
|
|
526
445
|
|
|
527
446
|
/**
|
|
528
|
-
* Recompute which terminal (if any) is the active main-area widget
|
|
529
|
-
* only
|
|
530
|
-
* current widget is one of our terminals; anything else — a notebook, an
|
|
531
|
-
* editor, or nothing — clears the highlight.
|
|
447
|
+
* Recompute which terminal (if any) is the active main-area widget; emits
|
|
448
|
+
* only on change. Anything not a tracked terminal clears the highlight.
|
|
532
449
|
*/
|
|
533
450
|
private _updateCurrent(): void {
|
|
534
451
|
const current = this._shell?.currentWidget ?? null;
|
|
@@ -565,16 +482,10 @@ export class SessionRegistry implements IDisposable {
|
|
|
565
482
|
}
|
|
566
483
|
|
|
567
484
|
/**
|
|
568
|
-
*
|
|
569
|
-
*
|
|
570
|
-
*
|
|
571
|
-
* the
|
|
572
|
-
* `_initialConnection` fires). Without the filter, briefly
|
|
573
|
-
* reopening a tab while an agent has yet to re-emit its xterm
|
|
574
|
-
* title escape sequence would clobber the cached agent name with
|
|
575
|
-
* `Terminal 1`. Live widget titles still display whatever the widget
|
|
576
|
-
* currently holds because `labelFor` consults the widget first;
|
|
577
|
-
* the filter only affects what survives a tab close.
|
|
485
|
+
* Cache a label only when it is "real" — not `'...'` or `Terminal {name}`,
|
|
486
|
+
* the transient defaults xterm cycles through during reconnect. Without the
|
|
487
|
+
* filter, reopening a tab before the agent re-emits its title escape would
|
|
488
|
+
* clobber the cached agent name.
|
|
578
489
|
*/
|
|
579
490
|
private _cacheLabel(name: string, label: string): void {
|
|
580
491
|
if (!label) {
|
|
@@ -587,11 +498,8 @@ export class SessionRegistry implements IDisposable {
|
|
|
587
498
|
}
|
|
588
499
|
|
|
589
500
|
private _onWidgetDisposed(widget: Widget): void {
|
|
590
|
-
// The
|
|
591
|
-
//
|
|
592
|
-
// when the user reopens the tab. We only clean up our subscriptions
|
|
593
|
-
// here; the cache is purged by `runningChanged` once the session
|
|
594
|
-
// itself goes away.
|
|
501
|
+
// The session may still be running on the server; the cached label stays
|
|
502
|
+
// until `runningChanged` purges it with the session itself.
|
|
595
503
|
this._untrackWidget(widget as TerminalWidget);
|
|
596
504
|
this._stateChanged.emit();
|
|
597
505
|
}
|
|
@@ -621,15 +529,21 @@ export class SessionRegistry implements IDisposable {
|
|
|
621
529
|
* Construction options for {@link SessionRegistry}.
|
|
622
530
|
*/
|
|
623
531
|
export namespace SessionRegistry {
|
|
532
|
+
/**
|
|
533
|
+
* The instantiation options for a session registry.
|
|
534
|
+
*/
|
|
624
535
|
export interface IOptions {
|
|
536
|
+
/**
|
|
537
|
+
* The service manager whose terminal manager supplies the sessions.
|
|
538
|
+
*/
|
|
625
539
|
serviceManager: ServiceManager.IManager;
|
|
540
|
+
/**
|
|
541
|
+
* The tracker of open terminal widgets.
|
|
542
|
+
*/
|
|
626
543
|
tracker: ITerminalTracker;
|
|
627
544
|
/**
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
* widget — and highlight nothing when that widget is not a terminal (a
|
|
631
|
-
* notebook, an editor, …). Optional: without it, or on a shell that cannot
|
|
632
|
-
* report `currentChanged`, the highlight stays off.
|
|
545
|
+
* Used to highlight the row of the current main-area terminal. Optional:
|
|
546
|
+
* without it (or its `currentChanged`) the highlight stays off.
|
|
633
547
|
*/
|
|
634
548
|
shell?: JupyterFrontEnd.IShell | null;
|
|
635
549
|
/**
|
|
@@ -638,19 +552,15 @@ export namespace SessionRegistry {
|
|
|
638
552
|
*/
|
|
639
553
|
agentSessions?: IAgentSessions | null;
|
|
640
554
|
/**
|
|
641
|
-
*
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
* matched by the process it spawns. Read on every poll so it tracks the
|
|
645
|
-
* live agent list.
|
|
555
|
+
* Names to detect: each agent's command plus its canonical id, so an
|
|
556
|
+
* aliased command (e.g. `ccm` running `claude`) is still matched by the
|
|
557
|
+
* process it spawns. Read on every poll.
|
|
646
558
|
*/
|
|
647
559
|
detectCommands?: () => string[];
|
|
648
560
|
/**
|
|
649
|
-
* Whether a detected command (or id) belongs to a coding agent rather
|
|
650
|
-
* an editor
|
|
651
|
-
*
|
|
652
|
-
* meaningfully "activity". Defaults to treating every detected command as an
|
|
653
|
-
* agent.
|
|
561
|
+
* Whether a detected command (or id) belongs to a coding agent rather
|
|
562
|
+
* than an editor; only agent sessions get a latest-activity line.
|
|
563
|
+
* Defaults to treating every detected command as an agent.
|
|
654
564
|
*/
|
|
655
565
|
isAgentCommand?: (command: string) => boolean;
|
|
656
566
|
}
|
|
@@ -658,8 +568,7 @@ export namespace SessionRegistry {
|
|
|
658
568
|
|
|
659
569
|
namespace Private {
|
|
660
570
|
/**
|
|
661
|
-
*
|
|
662
|
-
* doesn't trigger a re-render.
|
|
571
|
+
* Whether two detection maps hold identical entries.
|
|
663
572
|
*/
|
|
664
573
|
export function detectedEqual(
|
|
665
574
|
a: Map<string, string | null>,
|
|
@@ -676,36 +585,24 @@ namespace Private {
|
|
|
676
585
|
return true;
|
|
677
586
|
}
|
|
678
587
|
|
|
679
|
-
/**
|
|
680
|
-
* Rows above the cursor to scan when looking for the latest output.
|
|
681
|
-
*/
|
|
682
588
|
const ACTIVITY_SCAN_ROWS = 64;
|
|
683
589
|
|
|
684
|
-
/**
|
|
685
|
-
* Clamp for the activity string so one runaway block can't bloat a row.
|
|
686
|
-
*/
|
|
687
590
|
const ACTIVITY_MAX_LENGTH = 160;
|
|
688
591
|
|
|
689
592
|
/**
|
|
690
|
-
* Horizontal
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
* deliberately excluded — {@link isMeaningfulActivity} relies on them to
|
|
694
|
-
* recognise (and skip) the contents of an input/quote box.
|
|
593
|
+
* Horizontal rule characters treated as blanks when sanitizing (status-line
|
|
594
|
+
* padding is chrome, not text). Vertical bars are excluded on purpose —
|
|
595
|
+
* {@link isMeaningfulActivity} uses them to skip input/quote boxes.
|
|
695
596
|
*/
|
|
696
597
|
const RULE_CHARS = new Set([
|
|
697
598
|
0x2500, 0x2501, 0x2504, 0x2505, 0x2508, 0x2509, 0x254c, 0x254d, 0x2550
|
|
698
599
|
]);
|
|
699
600
|
|
|
700
601
|
/**
|
|
701
|
-
* The most recent meaningful line of agent output, or `null
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
* of real output nearest the input. It then rewinds to that block's first row
|
|
706
|
-
* and stitches the block back together (see {@link joinBlock}), so a reply
|
|
707
|
-
* the agent hard-wrapped across several rows reads as its opening sentence
|
|
708
|
-
* rather than the trailing fragment left next to the cursor.
|
|
602
|
+
* The most recent meaningful line of agent output, or `null`. Agents park
|
|
603
|
+
* the cursor in a bottom input box, so the scan walks *up* past chrome to
|
|
604
|
+
* the nearest output block, then rewinds to that block's first row so a
|
|
605
|
+
* hard-wrapped reply reads as its opening sentence, not a trailing fragment.
|
|
709
606
|
*/
|
|
710
607
|
export function readActivity(term: IXtermTerminal): string | null {
|
|
711
608
|
const buffer = term.buffer?.active;
|
|
@@ -718,8 +615,6 @@ namespace Private {
|
|
|
718
615
|
if (!isMeaningfulActivity(lineText(buffer, row))) {
|
|
719
616
|
continue;
|
|
720
617
|
}
|
|
721
|
-
// `row` is the bottom of the output block nearest the input; walk up
|
|
722
|
-
// while rows stay meaningful to find the block's first row.
|
|
723
618
|
let topRow = row;
|
|
724
619
|
while (
|
|
725
620
|
topRow > limit &&
|
|
@@ -733,17 +628,16 @@ namespace Private {
|
|
|
733
628
|
}
|
|
734
629
|
|
|
735
630
|
/**
|
|
736
|
-
*
|
|
631
|
+
* Read a buffer row as sanitized text; empty when the row is missing.
|
|
737
632
|
*/
|
|
738
633
|
export function lineText(buffer: IXtermBuffer, row: number): string {
|
|
739
634
|
return sanitizeActivity(buffer.getLine(row)?.translateToString(true) ?? '');
|
|
740
635
|
}
|
|
741
636
|
|
|
742
637
|
/**
|
|
743
|
-
* Stitch rows `[topRow, lastRow]` of one output block into a single string
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
* newline is a word boundary, so it is joined with a space.
|
|
638
|
+
* Stitch rows `[topRow, lastRow]` of one output block into a single string.
|
|
639
|
+
* A soft-wrapped row continues mid-word (appended directly); a hard newline
|
|
640
|
+
* is a word boundary (joined with a space).
|
|
747
641
|
*/
|
|
748
642
|
export function joinBlock(
|
|
749
643
|
buffer: IXtermBuffer,
|
|
@@ -776,9 +670,7 @@ namespace Private {
|
|
|
776
670
|
}
|
|
777
671
|
|
|
778
672
|
/**
|
|
779
|
-
*
|
|
780
|
-
* and horizontal rule characters with spaces (so no escape sequence or drawn
|
|
781
|
-
* separator rides along), squeeze runs of whitespace, and trim.
|
|
673
|
+
* Replace control and rule characters with spaces and collapse whitespace.
|
|
782
674
|
*/
|
|
783
675
|
export function sanitizeActivity(value: string): string {
|
|
784
676
|
let result = '';
|
|
@@ -791,18 +683,10 @@ namespace Private {
|
|
|
791
683
|
}
|
|
792
684
|
|
|
793
685
|
/**
|
|
794
|
-
* Whether a sanitized row is
|
|
795
|
-
*
|
|
796
|
-
*
|
|
797
|
-
*
|
|
798
|
-
* - begins with a vertical box-drawing bar (the contents of an input or
|
|
799
|
-
* quote box), a prompt chevron (the input row and its ghost suggestion),
|
|
800
|
-
* or Claude's tool-result / tip marker `⎿`; or
|
|
801
|
-
* - is a transient status footer — one carrying a `for <time>` / `(<time>`
|
|
802
|
-
* elapsed-time stamp, as agents print while and after they work
|
|
803
|
-
* ("✻ Churned for 17s", "Working… (8s · …)", "— Worked for 1m 06s"). This
|
|
804
|
-
* carries no real content, so skipping it lets the scan reach the reply
|
|
805
|
-
* the agent wrote just above it.
|
|
686
|
+
* Whether a sanitized row is real output rather than chrome; doubles as
|
|
687
|
+
* {@link readActivity}'s block-boundary test. Skips rows without a letter
|
|
688
|
+
* or digit, rows starting with a box bar / prompt chevron / `⎿`, and
|
|
689
|
+
* elapsed-time status footers ("✻ Churned for 17s", "Working… (8s · …)").
|
|
806
690
|
*/
|
|
807
691
|
export function isMeaningfulActivity(text: string): boolean {
|
|
808
692
|
if (!text) {
|