xtralab 0.10.0 → 0.11.0

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.
Files changed (71) hide show
  1. package/README.md +23 -9
  2. package/lib/commandBar/index.d.ts +14 -0
  3. package/lib/commandBar/index.js +114 -0
  4. package/lib/fileBrowser/fileBrowser.js +6 -1
  5. package/lib/highlight/index.d.ts +15 -0
  6. package/lib/highlight/index.js +214 -0
  7. package/lib/index.js +13 -1
  8. package/lib/launcher/agents.d.ts +7 -0
  9. package/lib/launcher/agents.js +24 -0
  10. package/lib/launcher/editorRegistry.js +5 -3
  11. package/lib/launcher/editors.d.ts +8 -0
  12. package/lib/launcher/editors.js +17 -0
  13. package/lib/launcher/index.js +4 -0
  14. package/lib/launcher/schemaDefaults.d.ts +15 -0
  15. package/lib/launcher/schemaDefaults.js +44 -0
  16. package/lib/omnibox/files.d.ts +7 -0
  17. package/lib/omnibox/files.js +50 -0
  18. package/lib/omnibox/index.d.ts +14 -0
  19. package/lib/omnibox/index.js +83 -0
  20. package/lib/omnibox/model.d.ts +45 -0
  21. package/lib/omnibox/model.js +142 -0
  22. package/lib/omnibox/tokens.d.ts +23 -0
  23. package/lib/omnibox/tokens.js +11 -0
  24. package/lib/omnibox/widget.d.ts +36 -0
  25. package/lib/omnibox/widget.js +161 -0
  26. package/lib/searchReplace/index.d.ts +25 -0
  27. package/lib/searchReplace/index.js +68 -0
  28. package/lib/showOutput/index.d.ts +17 -0
  29. package/lib/showOutput/index.js +154 -0
  30. package/lib/terminals/index.d.ts +4 -0
  31. package/lib/terminals/index.js +65 -16
  32. package/lib/terminals/model.d.ts +56 -3
  33. package/lib/terminals/model.js +261 -5
  34. package/lib/terminals/widget.d.ts +3 -1
  35. package/lib/terminals/widget.js +11 -3
  36. package/lib/walkthrough/index.d.ts +14 -0
  37. package/lib/walkthrough/index.js +162 -0
  38. package/lib/walkthrough/panel.d.ts +54 -0
  39. package/lib/walkthrough/panel.js +98 -0
  40. package/package.json +1 -1
  41. package/schema/launcher.json +3 -2
  42. package/schema/terminals.json +15 -0
  43. package/src/commandBar/index.ts +165 -0
  44. package/src/fileBrowser/fileBrowser.tsx +8 -1
  45. package/src/highlight/index.ts +251 -0
  46. package/src/index.ts +13 -1
  47. package/src/launcher/agents.ts +25 -0
  48. package/src/launcher/editorRegistry.ts +8 -4
  49. package/src/launcher/editors.ts +18 -0
  50. package/src/launcher/index.ts +4 -0
  51. package/src/launcher/schemaDefaults.ts +53 -0
  52. package/src/omnibox/files.ts +68 -0
  53. package/src/omnibox/index.ts +105 -0
  54. package/src/omnibox/model.ts +210 -0
  55. package/src/omnibox/tokens.ts +29 -0
  56. package/src/omnibox/widget.tsx +322 -0
  57. package/src/searchReplace/index.ts +86 -0
  58. package/src/showOutput/index.ts +199 -0
  59. package/src/terminals/index.ts +79 -16
  60. package/src/terminals/model.ts +304 -3
  61. package/src/terminals/widget.tsx +21 -4
  62. package/src/walkthrough/index.ts +202 -0
  63. package/src/walkthrough/panel.ts +152 -0
  64. package/style/commandBar.css +104 -0
  65. package/style/highlight.css +11 -0
  66. package/style/index.css +5 -0
  67. package/style/index.js +5 -0
  68. package/style/omnibox.css +120 -0
  69. package/style/showOutput.css +11 -0
  70. package/style/terminals.css +54 -10
  71. package/style/walkthrough.css +80 -0
@@ -0,0 +1,68 @@
1
+ import { ILabShell } from '@jupyterlab/application';
2
+ import { ICommandPalette } from '@jupyterlab/apputils';
3
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
4
+ const PLUGIN_ID = 'xtralab:search-replace';
5
+ const COMMAND_ID = 'xtralab:activate-search-replace';
6
+ /**
7
+ * Widget id of the panel contributed by `jupyterlab-search-replace`. The
8
+ * extension adds it to the left sidebar but registers no command of its own,
9
+ * so there is nothing to bind a shortcut to out of the box — this plugin adds
10
+ * the missing command.
11
+ */
12
+ const SEARCH_REPLACE_WIDGET_ID = 'jp-search-replace';
13
+ /**
14
+ * Bind Accel+Shift+F (Cmd+Shift+F on macOS, Ctrl+Shift+F elsewhere) to the
15
+ * project-wide Search and Replace panel, matching the equivalent shortcut in
16
+ * editors like VS Code.
17
+ *
18
+ * Two pieces are needed because that chord is already taken: JupyterLab core
19
+ * binds Accel+Shift+F to `filebrowser:toggle-main`.
20
+ *
21
+ * 1. The chord is freed declaratively. `SettingRegistry.reconcileShortcuts`
22
+ * keys collisions and disables on (keys, selector) — not on command — so the
23
+ * `@jupyterlab/shortcuts-extension:shortcuts` override in
24
+ * `default_setting_overrides.d/00-xtralab.json` disables the file-browser
25
+ * binding. The file browser stays reachable from its sidebar tab and the
26
+ * View menu; only the keyboard shortcut moves.
27
+ *
28
+ * 2. The new binding is added imperatively below rather than as another shipped
29
+ * default. A `disabled` default and a re-binding default land on the same
30
+ * (keys, selector) slot, and the disable suppresses every default on that
31
+ * slot — including the re-binding. `commands.addKeyBinding` runs outside the
32
+ * reconcile pass, so it is unaffected and wins cleanly once the file-browser
33
+ * binding is gone.
34
+ */
35
+ const plugin = {
36
+ id: PLUGIN_ID,
37
+ description: 'Activate the Search and Replace panel with Accel+Shift+F (Cmd/Ctrl+Shift+F).',
38
+ autoStart: true,
39
+ requires: [ILabShell],
40
+ optional: [ICommandPalette, ITranslator],
41
+ activate: (app, labShell, palette, translator) => {
42
+ const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
43
+ const { commands } = app;
44
+ commands.addCommand(COMMAND_ID, {
45
+ label: trans.__('Search and Replace'),
46
+ caption: trans.__('Show the Search and Replace panel'),
47
+ execute: () => {
48
+ // `activateById` expands the left sidebar when collapsed, selects the
49
+ // tab, and the widget focuses its search input from `onAfterShow`, so
50
+ // the user can start typing a query immediately. It no-ops if
51
+ // `jupyterlab-search-replace` is unavailable.
52
+ labShell.activateById(SEARCH_REPLACE_WIDGET_ID);
53
+ }
54
+ });
55
+ commands.addKeyBinding({
56
+ command: COMMAND_ID,
57
+ keys: ['Accel Shift F'],
58
+ selector: 'body'
59
+ });
60
+ if (palette) {
61
+ palette.addItem({
62
+ command: COMMAND_ID,
63
+ category: trans.__('File Operations')
64
+ });
65
+ }
66
+ }
67
+ };
68
+ export default plugin;
@@ -0,0 +1,17 @@
1
+ import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
+ /**
3
+ * Contribute `xtralab:show`: render a single MIME bundle into a main-area
4
+ * panel using the application's render-mime registry.
5
+ *
6
+ * This is the kernel-free counterpart to running a notebook. A coding agent
7
+ * generates the content itself (a Vega-Lite spec, a Markdown explainer, an
8
+ * HTML fragment, an SVG, a base64 image) and hands it to this command, which
9
+ * renders it with the same renderers JupyterLab uses for cell output and docks
10
+ * the result beside the code. Nothing is written to disk and no kernel is
11
+ * required, so it works even where the kernel has no plotting libraries.
12
+ *
13
+ * Repeated calls with the same `id` reuse one panel, so a walkthrough can
14
+ * refresh the view in place instead of piling up tabs.
15
+ */
16
+ declare const plugin: JupyterFrontEndPlugin<void>;
17
+ export default plugin;
@@ -0,0 +1,154 @@
1
+ import { ILabShell } from '@jupyterlab/application';
2
+ import { ICommandPalette } from '@jupyterlab/apputils';
3
+ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
4
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
5
+ import { Panel } from '@lumino/widgets';
6
+ const PLUGIN_ID = 'xtralab:show-output';
7
+ /** Render arbitrary rich content into a panel, with no notebook and no kernel. */
8
+ const SHOW_COMMAND = 'xtralab:show';
9
+ /**
10
+ * Parse a JSON-typed value that may have arrived as a string. Agents and the
11
+ * MCP bridge sometimes serialize a spec (e.g. a Vega-Lite object) to a string;
12
+ * for `application/...json` MIME types we accept either and parse the string.
13
+ */
14
+ function coerceData(mimeType, data) {
15
+ if (typeof data === 'string' && /\+?json$/.test(mimeType)) {
16
+ try {
17
+ return JSON.parse(data);
18
+ }
19
+ catch (_a) {
20
+ return data;
21
+ }
22
+ }
23
+ return data;
24
+ }
25
+ /**
26
+ * Contribute `xtralab:show`: render a single MIME bundle into a main-area
27
+ * panel using the application's render-mime registry.
28
+ *
29
+ * This is the kernel-free counterpart to running a notebook. A coding agent
30
+ * generates the content itself (a Vega-Lite spec, a Markdown explainer, an
31
+ * HTML fragment, an SVG, a base64 image) and hands it to this command, which
32
+ * renders it with the same renderers JupyterLab uses for cell output and docks
33
+ * the result beside the code. Nothing is written to disk and no kernel is
34
+ * required, so it works even where the kernel has no plotting libraries.
35
+ *
36
+ * Repeated calls with the same `id` reuse one panel, so a walkthrough can
37
+ * refresh the view in place instead of piling up tabs.
38
+ */
39
+ const plugin = {
40
+ id: PLUGIN_ID,
41
+ description: 'Render rich content (Markdown, Vega-Lite, HTML, images) into a panel.',
42
+ autoStart: true,
43
+ requires: [IRenderMimeRegistry, ILabShell],
44
+ optional: [ICommandPalette, ITranslator],
45
+ activate: (app, rendermime, labShell, palette, translator) => {
46
+ const { commands } = app;
47
+ const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
48
+ // One reusable panel per `id`, so repeated shows refresh in place.
49
+ const panels = new Map();
50
+ commands.addCommand(SHOW_COMMAND, {
51
+ label: trans.__('Show Rich Output'),
52
+ caption: trans.__('Render Markdown, a Vega-Lite chart, HTML, or an image in a panel'),
53
+ describedBy: {
54
+ args: {
55
+ type: 'object',
56
+ required: ['mimeType', 'data'],
57
+ properties: {
58
+ mimeType: {
59
+ type: 'string',
60
+ description: 'MIME type to render, e.g. "text/markdown", "application/vnd.vegalite.v5+json", "text/html", "image/svg+xml", "image/png".'
61
+ },
62
+ data: {
63
+ description: 'The content. A string for text/image types (base64 for "image/png"); an object for JSON types such as a Vega-Lite spec.'
64
+ },
65
+ label: {
66
+ type: 'string',
67
+ description: 'Title for the panel tab. Defaults to "Output".'
68
+ },
69
+ id: {
70
+ type: 'string',
71
+ description: 'Panel identifier. Calls sharing an `id` reuse one panel; defaults to "default".'
72
+ },
73
+ area: {
74
+ type: 'string',
75
+ description: 'Where the panel docks: "right" (default) puts it in the side area so the editor stays full width; "main" puts it in the document area (split with `mode`).'
76
+ },
77
+ mode: {
78
+ type: 'string',
79
+ description: 'Only when `area` is "main": placement relative to the active tab, e.g. "split-right" (default), "split-bottom", "tab-after".'
80
+ }
81
+ }
82
+ }
83
+ },
84
+ execute: async (args) => {
85
+ const mimeType = typeof args['mimeType'] === 'string' ? args['mimeType'] : undefined;
86
+ if (!mimeType || args['data'] === undefined) {
87
+ throw new Error('xtralab:show requires "mimeType" and "data"');
88
+ }
89
+ const data = coerceData(mimeType, args['data']);
90
+ const label = typeof args['label'] === 'string'
91
+ ? args['label']
92
+ : trans.__('Output');
93
+ const id = typeof args['id'] === 'string' ? args['id'] : 'default';
94
+ const area = args['area'] === 'main' ? 'main' : 'right';
95
+ const mode = typeof args['mode'] === 'string' ? args['mode'] : 'split-right';
96
+ const bundle = { [mimeType]: data };
97
+ const chosen = rendermime.preferredMimeType(bundle, 'any');
98
+ if (!chosen) {
99
+ throw new Error(`xtralab:show: no renderer for MIME type "${mimeType}"`);
100
+ }
101
+ const model = rendermime.createModel({ data: bundle, trusted: true });
102
+ const renderer = rendermime.createRenderer(chosen);
103
+ // A scrollable Panel, not a MainAreaWidget: MainAreaWidget does not lay
104
+ // out its content in the side area, and the panel needs to scroll when
105
+ // the content is taller than it (style/showOutput.css).
106
+ const widget = new Panel();
107
+ widget.addClass('jp-xtralab-ShowPanel');
108
+ widget.id = `xtralab-show-${id}`;
109
+ widget.title.label = label;
110
+ widget.title.closable = true;
111
+ widget.addWidget(renderer);
112
+ // Replace any existing panel with this id so shows refresh in place.
113
+ const existing = panels.get(id);
114
+ if (existing && !existing.isDisposed) {
115
+ existing.dispose();
116
+ }
117
+ panels.set(id, widget);
118
+ widget.disposed.connect(() => {
119
+ if (panels.get(id) === widget) {
120
+ panels.delete(id);
121
+ }
122
+ });
123
+ // Attach before rendering so renderers that need layout (Vega) size
124
+ // correctly. Default to the side area so the editor keeps full width;
125
+ // only split the document area when `area` is "main".
126
+ if (area === 'main') {
127
+ labShell.add(widget, 'main', {
128
+ mode: mode,
129
+ activate: true
130
+ });
131
+ }
132
+ else {
133
+ labShell.add(widget, 'right', { rank: 900 });
134
+ }
135
+ // Reveal the panel (the side area does not open from `add` alone).
136
+ labShell.activateById(widget.id);
137
+ try {
138
+ await renderer.renderModel(model);
139
+ }
140
+ catch (error) {
141
+ widget.dispose();
142
+ throw error instanceof Error
143
+ ? error
144
+ : new Error(`xtralab:show: failed to render "${mimeType}"`);
145
+ }
146
+ return trans.__('Rendered %1 in "%2"', mimeType, label);
147
+ }
148
+ });
149
+ if (palette) {
150
+ palette.addItem({ command: SHOW_COMMAND, category: trans.__('Other') });
151
+ }
152
+ }
153
+ };
154
+ export default plugin;
@@ -9,6 +9,10 @@ import { JupyterFrontEndPlugin } from '@jupyterlab/application';
9
9
  * JupyterLab's built-in "Running Terminals and Kernels" panel lists the
10
10
  * same sessions but only by their `terminals/<n>` name, so this panel
11
11
  * resolves and caches the published title to show something meaningful.
12
+ * Rows running a coding agent carry a smaller second line below the
13
+ * title with the agent's latest line of output, so the panel shows what
14
+ * each agent is doing at a glance — on by default, and switchable off
15
+ * through the `showAgentActivity` setting.
12
16
  *
13
17
  * Clicking a row activates the existing tab if one is open, or reopens
14
18
  * the session in a fresh terminal widget (`terminal:open`). The inline
@@ -1,5 +1,6 @@
1
1
  import { ILayoutRestorer } from '@jupyterlab/application';
2
2
  import { Dialog, showDialog } from '@jupyterlab/apputils';
3
+ import { ISettingRegistry } from '@jupyterlab/settingregistry';
3
4
  import { ITerminalTracker } from '@jupyterlab/terminal';
4
5
  import { ITranslator, nullTranslator } from '@jupyterlab/translation';
5
6
  import { MenuSvg, terminalIcon } from '@jupyterlab/ui-components';
@@ -19,6 +20,10 @@ const PLUGIN_ID = 'xtralab:terminals';
19
20
  * JupyterLab's built-in "Running Terminals and Kernels" panel lists the
20
21
  * same sessions but only by their `terminals/<n>` name, so this panel
21
22
  * resolves and caches the published title to show something meaningful.
23
+ * Rows running a coding agent carry a smaller second line below the
24
+ * title with the agent's latest line of output, so the panel shows what
25
+ * each agent is doing at a glance — on by default, and switchable off
26
+ * through the `showAgentActivity` setting.
22
27
  *
23
28
  * Clicking a row activates the existing tab if one is open, or reopens
24
29
  * the session in a fresh terminal widget (`terminal:open`). The inline
@@ -43,26 +48,30 @@ const plugin = {
43
48
  ITranslator,
44
49
  IAgentRegistry,
45
50
  IEditorRegistry,
46
- IAgentSessions
51
+ IAgentSessions,
52
+ ISettingRegistry
47
53
  ],
48
- activate: (app, tracker, restorer, translator, agentRegistry, editorRegistry, agentSessions) => {
54
+ activate: (app, tracker, restorer, translator, agentRegistry, editorRegistry, agentSessions, settingRegistry) => {
49
55
  const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
50
- // Resolve a session's running command to an icon: the matching agent's
51
- // logo, the matching editor's logo (Neovim/Vim), or the plain terminal
52
- // icon when nothing recognised is running. Reads `agentRegistry.agents`
53
- // live so it tracks settings changes. Used by the panel to badge each row.
56
+ // Resolve a running agent/editor identifier to an icon: the matching
57
+ // agent's logo, the matching editor's logo (Neovim/Vim), or the plain
58
+ // terminal icon when nothing recognised is running. Detection reports
59
+ // either the configured `command` or the canonical `id` (see
60
+ // `detectCommands`), so a row whose command is an alias — e.g. `ccm` for
61
+ // `claude` — still resolves to the right logo via its id. Reads the
62
+ // registries live so it tracks settings changes. Used to badge each row.
54
63
  const iconForCommand = (command) => {
55
64
  var _a;
56
65
  if (!command) {
57
66
  return terminalIcon;
58
67
  }
59
- const agent = agentRegistry === null || agentRegistry === void 0 ? void 0 : agentRegistry.agents.find(a => a.command === command);
68
+ const agent = agentRegistry === null || agentRegistry === void 0 ? void 0 : agentRegistry.agents.find(a => a.command === command || a.id === command);
60
69
  if (agent) {
61
70
  return agent.icon;
62
71
  }
63
72
  // A terminal running an editor (Neovim/Vim, or a user-configured one) is
64
73
  // badged with its logo, the same as an agent.
65
- const editor = editorRegistry === null || editorRegistry === void 0 ? void 0 : editorRegistry.editors.find(e => e.command === command);
74
+ const editor = editorRegistry === null || editorRegistry === void 0 ? void 0 : editorRegistry.editors.find(e => e.command === command || e.id === command);
66
75
  return (_a = editor === null || editor === void 0 ? void 0 : editor.icon) !== null && _a !== void 0 ? _a : terminalIcon;
67
76
  };
68
77
  const registry = new SessionRegistry({
@@ -73,16 +82,32 @@ const plugin = {
73
82
  // nothing while a notebook or other widget is current instead).
74
83
  shell: app.shell,
75
84
  agentSessions,
76
- // The commands the server should look for: the current agent and editor
77
- // lists, each read live from its registry so they track settings changes
78
- // — a terminal running an agent or an editor (Neovim/Vim, or a configured
79
- // one) is badged.
85
+ // The names the server should look for in each terminal's process tree:
86
+ // for every agent and editor, its configured `command` plus its canonical
87
+ // `id`. The id is the real CLI name (e.g. `claude`, `nvim`), so an agent
88
+ // whose command points at an alias — e.g. `ccm` running `claude` — is
89
+ // still recognised by the process it actually spawns. Read live so the
90
+ // list tracks settings changes; deduplicated since command and id usually
91
+ // coincide.
80
92
  detectCommands: () => {
81
93
  var _a, _b;
82
- return [
83
- ...((_a = agentRegistry === null || agentRegistry === void 0 ? void 0 : agentRegistry.agents.map(a => a.command)) !== null && _a !== void 0 ? _a : []),
84
- ...((_b = editorRegistry === null || editorRegistry === void 0 ? void 0 : editorRegistry.editors.map(e => e.command)) !== null && _b !== void 0 ? _b : [])
85
- ];
94
+ const names = new Set();
95
+ for (const agent of (_a = agentRegistry === null || agentRegistry === void 0 ? void 0 : agentRegistry.agents) !== null && _a !== void 0 ? _a : []) {
96
+ names.add(agent.command);
97
+ names.add(agent.id);
98
+ }
99
+ for (const editor of (_b = editorRegistry === null || editorRegistry === void 0 ? void 0 : editorRegistry.editors) !== null && _b !== void 0 ? _b : []) {
100
+ names.add(editor.command);
101
+ names.add(editor.id);
102
+ }
103
+ return Array.from(names);
104
+ },
105
+ // Only coding agents (not editors) get a latest-activity line, so the
106
+ // registry asks this whether a detected command/id is an agent before
107
+ // reading its output buffer — editor terminals stay badge-only.
108
+ isAgentCommand: (command) => {
109
+ var _a;
110
+ return (_a = agentRegistry === null || agentRegistry === void 0 ? void 0 : agentRegistry.agents.some(a => a.command === command || a.id === command)) !== null && _a !== void 0 ? _a : false;
86
111
  }
87
112
  });
88
113
  // Mirror the agent/editor detected in each open terminal onto its main-area
@@ -189,6 +214,30 @@ const plugin = {
189
214
  if (restorer) {
190
215
  restorer.add(panel, panel.id);
191
216
  }
217
+ // The latest-activity line under each agent row is on by default; honour
218
+ // the `showAgentActivity` setting so it can be turned off. Read on load and
219
+ // re-applied on every change.
220
+ if (settingRegistry) {
221
+ settingRegistry
222
+ .load(PLUGIN_ID)
223
+ .then(settings => {
224
+ const applyActivitySetting = () => {
225
+ registry.setActivityEnabled(boolOption(settings.composite.showAgentActivity, true));
226
+ };
227
+ applyActivitySetting();
228
+ // Bind the slot to the panel so disposing the panel (which clears
229
+ // its signal data) drops the connection — the disposed registry is
230
+ // then never reached on a later settings change.
231
+ settings.changed.connect(applyActivitySetting, panel);
232
+ })
233
+ .catch(reason => {
234
+ console.error(`xtralab: failed to load ${PLUGIN_ID} settings`, reason);
235
+ });
236
+ }
192
237
  }
193
238
  };
239
+ /** Read a boolean setting value, falling back when it is missing or invalid. */
240
+ function boolOption(value, fallback) {
241
+ return typeof value === 'boolean' ? value : fallback;
242
+ }
194
243
  export default plugin;
@@ -17,6 +17,11 @@ export type TerminalWidget = MainAreaWidget<ITerminal.ITerminal>;
17
17
  * xterm-published title) survives the user closing the tab while the
18
18
  * session continues running on the backend.
19
19
  *
20
+ * For sessions running a coding agent it also surfaces a *latest activity*
21
+ * line — the freshest meaningful line of the terminal's output, read from the
22
+ * open tab's xterm buffer on a poll (see {@link activityFor}) — so each row
23
+ * shows what its agent is doing, not just its name.
24
+ *
20
25
  * It also resolves *which agent is running* in each session, so the panel can
21
26
  * badge rows with the agent's logo. Two inputs feed that, reconciled by
22
27
  * {@link agentCommandFor}:
@@ -69,7 +74,10 @@ export declare class SessionRegistry implements IDisposable {
69
74
  */
70
75
  labelFor(name: string): string;
71
76
  /**
72
- * The command of the agent running in the session, or `null` if none.
77
+ * An identifier for the agent running in the session — its configured
78
+ * command, or its canonical id when detection matched the spawned process by
79
+ * id rather than by an aliased command — or `null` if none. Callers resolve
80
+ * it to a logo through the plugin's `iconForCommand`, which accepts either.
73
81
  *
74
82
  * Server-side detection is authoritative whenever it reports a running
75
83
  * agent. A launch tag fills two gaps: the startup grace window right after
@@ -80,6 +88,24 @@ export declare class SessionRegistry implements IDisposable {
80
88
  * exits.
81
89
  */
82
90
  agentCommandFor(name: string): string | null;
91
+ /**
92
+ * The most recent meaningful line of output from the session's terminal, or
93
+ * `null` when there is nothing to surface — no coding agent is running in the
94
+ * session, its tab is closed (so there is no live buffer to read), or the
95
+ * buffer holds only the agent's input box and chrome. Refreshed on a poll by
96
+ * {@link _refreshActivity}; shown as a smaller line under the row's title.
97
+ * Always `null` while the activity line is disabled (see
98
+ * {@link setActivityEnabled}).
99
+ */
100
+ activityFor(name: string): string | null;
101
+ /**
102
+ * Turn the per-row latest-activity line on or off, driven by the
103
+ * `xtralab:terminals` `showAgentActivity` setting. When turned off the
104
+ * activity poll stops reading terminal buffers and any cached lines are
105
+ * dropped, so rows fall back to their title alone; when turned back on the
106
+ * lines are repopulated on the spot. A no-op when the value is unchanged.
107
+ */
108
+ setActivityEnabled(enabled: boolean): void;
83
109
  /**
84
110
  * Return the open widget for a session, if any. The panel uses this to
85
111
  * switch behavior between "activate existing tab" and "open a new tab
@@ -118,6 +144,18 @@ export declare class SessionRegistry implements IDisposable {
118
144
  * clearing them, so a transient error doesn't strip every badge.
119
145
  */
120
146
  private _refreshDetection;
147
+ /**
148
+ * Poll body: re-read the buffer of each open coding-agent terminal and cache
149
+ * its latest meaningful line of output. Only sessions that have a running
150
+ * *agent* (not an editor) and an open tab qualify — a closed tab has no live
151
+ * buffer, editors run full-screen UIs that are not "activity", and plain
152
+ * shells would only echo their prompt. While a tab is reopening (its xterm
153
+ * is not ready yet) any existing line is kept; once the buffer is readable
154
+ * but has nothing worth showing the line is dropped, so a screen the agent
155
+ * has cleared doesn't leave a frozen line behind. Cached lines for sessions
156
+ * that no longer qualify are pruned. Emits only when something changed.
157
+ */
158
+ private _refreshActivity;
121
159
  private _onTagChanged;
122
160
  private _onWidgetAdded;
123
161
  private _onShellCurrentChanged;
@@ -150,12 +188,16 @@ export declare class SessionRegistry implements IDisposable {
150
188
  private _tracker;
151
189
  private _agentSessions;
152
190
  private _detectCommands;
191
+ private _isAgentCommand;
153
192
  private _shell;
154
193
  private _poll;
194
+ private _activityPoll;
155
195
  private _labels;
156
196
  private _ranks;
157
197
  private _firstSeen;
158
198
  private _detected;
199
+ private _activity;
200
+ private _activityEnabled;
159
201
  private _live;
160
202
  private _currentName;
161
203
  private _nextRank;
@@ -183,9 +225,20 @@ export declare namespace SessionRegistry {
183
225
  */
184
226
  agentSessions?: IAgentSessions | null;
185
227
  /**
186
- * Returns the agent commands the server should look for when detecting
187
- * running agents. Read on every poll so it tracks the live agent list.
228
+ * Returns the names the server should look for when detecting running
229
+ * agents — each agent's command together with its canonical id, so a
230
+ * command pointed at an alias (e.g. `ccm` running `claude`) is still
231
+ * matched by the process it spawns. Read on every poll so it tracks the
232
+ * live agent list.
188
233
  */
189
234
  detectCommands?: () => string[];
235
+ /**
236
+ * Whether a detected command (or id) belongs to a coding agent rather than
237
+ * an editor. Only agent sessions get a latest-activity line — editors
238
+ * (Neovim/Vim) are badged too, but run full-screen UIs whose buffer is not
239
+ * meaningfully "activity". Defaults to treating every detected command as an
240
+ * agent.
241
+ */
242
+ isAgentCommand?: (command: string) => boolean;
190
243
  }
191
244
  }