xtralab 0.11.1 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +38 -231
  2. package/lib/agentSessions.d.ts +6 -2
  3. package/lib/askAgent/editorSelection.d.ts +53 -0
  4. package/lib/askAgent/editorSelection.js +95 -0
  5. package/lib/askAgent/icons.d.ts +7 -0
  6. package/lib/askAgent/icons.js +15 -0
  7. package/lib/askAgent/index.d.ts +32 -0
  8. package/lib/askAgent/index.js +741 -0
  9. package/lib/askAgent/popup.d.ts +83 -0
  10. package/lib/askAgent/popup.js +221 -0
  11. package/lib/askAgent/prompt.d.ts +24 -0
  12. package/lib/askAgent/prompt.js +116 -0
  13. package/lib/askAgent/queue.d.ts +93 -0
  14. package/lib/askAgent/queue.js +207 -0
  15. package/lib/askAgent/queuePanel.d.ts +76 -0
  16. package/lib/askAgent/queuePanel.js +188 -0
  17. package/lib/askAgent/targetPicker.d.ts +58 -0
  18. package/lib/askAgent/targetPicker.js +74 -0
  19. package/lib/askAgent/tokens.d.ts +124 -0
  20. package/lib/askAgent/tokens.js +13 -0
  21. package/lib/commandBar/index.d.ts +12 -8
  22. package/lib/commandBar/index.js +58 -41
  23. package/lib/editorBreadcrumbs/index.js +9 -4
  24. package/lib/editorBreadcrumbs/widget.d.ts +7 -7
  25. package/lib/editorBreadcrumbs/widget.js +6 -8
  26. package/lib/fileBrowser/commands.d.ts +2 -8
  27. package/lib/fileBrowser/commands.js +39 -30
  28. package/lib/fileBrowser/contents.d.ts +8 -3
  29. package/lib/fileBrowser/fileBrowser.d.ts +2 -1
  30. package/lib/fileBrowser/widget.d.ts +5 -9
  31. package/lib/fileBrowser/widget.js +1 -1
  32. package/lib/fileTypeIcons/index.js +5 -2
  33. package/lib/git/api.d.ts +0 -26
  34. package/lib/git/api.js +2 -44
  35. package/lib/git/askRequest.d.ts +23 -0
  36. package/lib/git/askRequest.js +90 -0
  37. package/lib/git/commands.d.ts +14 -2
  38. package/lib/git/commands.js +16 -13
  39. package/lib/git/diffModel.d.ts +1 -9
  40. package/lib/git/diffModel.js +1 -1
  41. package/lib/git/diffProvider.js +8 -3
  42. package/lib/git/diffSurface.d.ts +19 -2
  43. package/lib/git/diffSurface.js +64 -18
  44. package/lib/git/diffTheme.d.ts +0 -4
  45. package/lib/git/diffTheme.js +1 -1
  46. package/lib/git/diffWidget.d.ts +9 -0
  47. package/lib/git/diffWidget.js +38 -10
  48. package/lib/git/imageDiff.d.ts +17 -4
  49. package/lib/git/imageDiff.js +90 -30
  50. package/lib/git/index.js +7 -2
  51. package/lib/git/notebookDiff.d.ts +10 -67
  52. package/lib/git/notebookDiff.js +62 -69
  53. package/lib/git/tokens.d.ts +5 -2
  54. package/lib/highlight/index.js +12 -8
  55. package/lib/index.js +2 -0
  56. package/lib/launcher/agents.js +3 -4
  57. package/lib/launcher/commands.d.ts +6 -2
  58. package/lib/launcher/dashboard.d.ts +8 -1
  59. package/lib/launcher/dashboard.js +39 -33
  60. package/lib/launcher/editorRegistry.js +5 -3
  61. package/lib/launcher/editors.d.ts +15 -5
  62. package/lib/launcher/icons.d.ts +0 -37
  63. package/lib/launcher/icons.js +11 -12
  64. package/lib/launcher/index.js +2 -1
  65. package/lib/launcher/invocation.d.ts +0 -15
  66. package/lib/launcher/invocation.js +1 -1
  67. package/lib/launcher/schemaDefaults.d.ts +3 -1
  68. package/lib/launcher/schemaDefaults.js +3 -1
  69. package/lib/mimeData.d.ts +7 -0
  70. package/lib/mimeData.js +16 -0
  71. package/lib/omnibox/index.d.ts +4 -0
  72. package/lib/omnibox/index.js +43 -2
  73. package/lib/omnibox/model.d.ts +43 -14
  74. package/lib/omnibox/model.js +117 -14
  75. package/lib/omnibox/recents.d.ts +65 -0
  76. package/lib/omnibox/recents.js +109 -0
  77. package/lib/omnibox/tokens.d.ts +6 -2
  78. package/lib/omnibox/widget.d.ts +23 -6
  79. package/lib/omnibox/widget.js +26 -7
  80. package/lib/showOutput/index.js +15 -18
  81. package/lib/terminals/agentTerminals.d.ts +63 -0
  82. package/lib/terminals/agentTerminals.js +157 -0
  83. package/lib/terminals/index.d.ts +7 -1
  84. package/lib/terminals/index.js +50 -30
  85. package/lib/terminals/model.d.ts +11 -2
  86. package/lib/terminals/model.js +27 -8
  87. package/lib/terminals/tokens.d.ts +66 -0
  88. package/lib/terminals/tokens.js +7 -0
  89. package/lib/terminals/widget.d.ts +3 -6
  90. package/lib/terminals/widget.js +1 -1
  91. package/lib/topBar/index.js +3 -3
  92. package/lib/walkthrough/index.js +14 -3
  93. package/lib/walkthrough/panel.d.ts +12 -4
  94. package/lib/walkthrough/panel.js +9 -3
  95. package/package.json +49 -51
  96. package/schema/command-bar.json +10 -0
  97. package/schema/omnibox.json +15 -0
  98. package/src/agentSessions.ts +6 -2
  99. package/src/askAgent/editorSelection.ts +138 -0
  100. package/src/askAgent/icons.ts +16 -0
  101. package/src/askAgent/index.ts +915 -0
  102. package/src/askAgent/popup.tsx +407 -0
  103. package/src/askAgent/prompt.ts +144 -0
  104. package/src/askAgent/queue.ts +269 -0
  105. package/src/askAgent/queuePanel.tsx +418 -0
  106. package/src/askAgent/targetPicker.tsx +209 -0
  107. package/src/askAgent/tokens.ts +139 -0
  108. package/src/commandBar/index.ts +75 -49
  109. package/src/editorBreadcrumbs/index.ts +14 -4
  110. package/src/editorBreadcrumbs/widget.ts +11 -9
  111. package/src/fileBrowser/commands.ts +45 -31
  112. package/src/fileBrowser/contents.ts +7 -3
  113. package/src/fileBrowser/fileBrowser.tsx +1 -1
  114. package/src/fileBrowser/widget.tsx +5 -3
  115. package/src/fileTypeIcons/index.ts +6 -3
  116. package/src/git/api.ts +2 -61
  117. package/src/git/askRequest.ts +113 -0
  118. package/src/git/commands.ts +41 -14
  119. package/src/git/diffModel.ts +1 -1
  120. package/src/git/diffProvider.tsx +11 -3
  121. package/src/git/diffSurface.tsx +114 -22
  122. package/src/git/diffTheme.ts +1 -1
  123. package/src/git/diffWidget.tsx +54 -7
  124. package/src/git/imageDiff.tsx +169 -58
  125. package/src/git/index.ts +10 -2
  126. package/src/git/notebookDiff.tsx +91 -57
  127. package/src/git/tokens.ts +4 -2
  128. package/src/highlight/index.ts +12 -8
  129. package/src/index.ts +2 -0
  130. package/src/launcher/agents.ts +3 -4
  131. package/src/launcher/commands.ts +6 -2
  132. package/src/launcher/dashboard.tsx +86 -38
  133. package/src/launcher/editorRegistry.ts +10 -4
  134. package/src/launcher/editors.ts +15 -5
  135. package/src/launcher/icons.ts +11 -12
  136. package/src/launcher/index.ts +2 -1
  137. package/src/launcher/invocation.ts +1 -1
  138. package/src/launcher/schemaDefaults.ts +3 -1
  139. package/src/mimeData.ts +20 -0
  140. package/src/omnibox/index.ts +48 -1
  141. package/src/omnibox/model.ts +173 -33
  142. package/src/omnibox/recents.ts +155 -0
  143. package/src/omnibox/tokens.ts +6 -2
  144. package/src/omnibox/widget.tsx +54 -13
  145. package/src/showOutput/index.ts +20 -23
  146. package/src/terminals/agentTerminals.ts +238 -0
  147. package/src/terminals/index.ts +54 -30
  148. package/src/terminals/model.ts +28 -7
  149. package/src/terminals/tokens.ts +77 -0
  150. package/src/terminals/widget.tsx +4 -2
  151. package/src/topBar/index.ts +12 -6
  152. package/src/walkthrough/index.ts +19 -4
  153. package/src/walkthrough/panel.ts +15 -5
  154. package/style/askAgent.css +544 -0
  155. package/style/commandBar.css +12 -33
  156. package/style/git.css +72 -31
  157. package/style/index.css +1 -0
  158. package/style/index.js +2 -0
  159. package/style/topBar.css +4 -4
@@ -0,0 +1,207 @@
1
+ import { Signal } from '@lumino/signaling';
2
+ let idCounter = 0;
3
+ function nextId() {
4
+ return `queued-${Date.now().toString(36)}-${idCounter++}`;
5
+ }
6
+ /**
7
+ * The accumulated ask-agent prompts awaiting a batch send.
8
+ *
9
+ * Queueing decouples writing review comments from deciding where they go:
10
+ * the popup appends entries here instead of sending, the side panel lists
11
+ * and edits them, and one batch send flushes the lot, delivering each
12
+ * destination's prompts as one numbered message. The queue itself is a
13
+ * plain in-memory model; the plugin mirrors it into the JupyterLab state
14
+ * database (via {@link serializeQueuedPrompts}) so a page reload does not
15
+ * silently drop typed comments.
16
+ */
17
+ export class PromptQueue {
18
+ constructor(items = []) {
19
+ this._changed = new Signal(this);
20
+ this._items = items;
21
+ }
22
+ /**
23
+ * The queued prompts, oldest first.
24
+ */
25
+ get items() {
26
+ return this._items;
27
+ }
28
+ /**
29
+ * Emitted whenever the queue contents change.
30
+ */
31
+ get changed() {
32
+ return this._changed;
33
+ }
34
+ /**
35
+ * Append a prompt to the queue.
36
+ */
37
+ add(context, instruction, target) {
38
+ this._items = [
39
+ ...this._items,
40
+ { id: nextId(), context, instruction, target }
41
+ ];
42
+ this._changed.emit();
43
+ }
44
+ /**
45
+ * Replace the instruction of the queued prompt `id`, if still present.
46
+ */
47
+ updateInstruction(id, instruction) {
48
+ let touched = false;
49
+ this._items = this._items.map(item => {
50
+ if (item.id !== id || item.instruction === instruction) {
51
+ return item;
52
+ }
53
+ touched = true;
54
+ return { ...item, instruction };
55
+ });
56
+ if (touched) {
57
+ this._changed.emit();
58
+ }
59
+ }
60
+ /**
61
+ * Repoint the queued prompt `id` at `target`, if still present.
62
+ */
63
+ updateTarget(id, target) {
64
+ let touched = false;
65
+ this._items = this._items.map(item => {
66
+ if (item.id !== id) {
67
+ return item;
68
+ }
69
+ touched = true;
70
+ return { ...item, target };
71
+ });
72
+ if (touched) {
73
+ this._changed.emit();
74
+ }
75
+ }
76
+ /**
77
+ * Remove the queued prompt `id`, if still present.
78
+ */
79
+ remove(id) {
80
+ this.removeMany([id]);
81
+ }
82
+ /**
83
+ * Remove several queued prompts at once (one signal emission) — used when
84
+ * a batch send succeeds for one target while other targets' prompts stay.
85
+ */
86
+ removeMany(ids) {
87
+ const drop = new Set(ids);
88
+ const remaining = this._items.filter(item => !drop.has(item.id));
89
+ if (remaining.length !== this._items.length) {
90
+ this._items = remaining;
91
+ this._changed.emit();
92
+ }
93
+ }
94
+ /**
95
+ * Drop every queued prompt.
96
+ */
97
+ clear() {
98
+ if (this._items.length > 0) {
99
+ this._items = [];
100
+ this._changed.emit();
101
+ }
102
+ }
103
+ /**
104
+ * Replace the whole queue (used to restore and to undo a clear).
105
+ */
106
+ reset(items) {
107
+ this._items = [...items];
108
+ this._changed.emit();
109
+ }
110
+ }
111
+ /**
112
+ * Validate one persisted context. Only fields that still match the
113
+ * `IAskAgentContext` contract are kept, so schema drift (or manual
114
+ * state-database edits) degrades an entry instead of poisoning the queue.
115
+ */
116
+ function sanitizeContext(value) {
117
+ if (value === null || typeof value !== 'object') {
118
+ return null;
119
+ }
120
+ const raw = value;
121
+ if (typeof raw.path !== 'string' || raw.path.length === 0) {
122
+ return null;
123
+ }
124
+ const context = {
125
+ path: raw.path,
126
+ text: typeof raw.text === 'string' ? raw.text : ''
127
+ };
128
+ if (typeof raw.cwd === 'string') {
129
+ context.cwd = raw.cwd;
130
+ }
131
+ const cell = raw.cell;
132
+ if (cell !== null &&
133
+ typeof cell === 'object' &&
134
+ typeof cell.index === 'number' &&
135
+ typeof cell.type === 'string') {
136
+ context.cell = { index: cell.index, type: cell.type };
137
+ }
138
+ if (typeof raw.startLine === 'number') {
139
+ context.startLine = raw.startLine;
140
+ }
141
+ if (typeof raw.endLine === 'number') {
142
+ context.endLine = raw.endLine;
143
+ }
144
+ if (typeof raw.linesInWorkingFile === 'boolean') {
145
+ context.linesInWorkingFile = raw.linesInWorkingFile;
146
+ }
147
+ if (typeof raw.location === 'string') {
148
+ context.location = raw.location;
149
+ }
150
+ if (typeof raw.note === 'string') {
151
+ context.note = raw.note;
152
+ }
153
+ return context;
154
+ }
155
+ /**
156
+ * Validate one persisted target; anything that does not parse becomes
157
+ * `null`, which the panel surfaces as "pick where to send this".
158
+ */
159
+ function sanitizeTarget(value) {
160
+ if (value === null || typeof value !== 'object') {
161
+ return null;
162
+ }
163
+ const raw = value;
164
+ if (raw.kind === 'new' && typeof raw.agentId === 'string') {
165
+ return { kind: 'new', agentId: raw.agentId };
166
+ }
167
+ if (raw.kind === 'session' && typeof raw.name === 'string') {
168
+ return { kind: 'session', name: raw.name };
169
+ }
170
+ return null;
171
+ }
172
+ /**
173
+ * Rebuild queued prompts from a value read back from the state database.
174
+ * Entries that no longer parse are dropped silently — the queue is a
175
+ * convenience buffer, not a document.
176
+ */
177
+ export function deserializeQueuedPrompts(value) {
178
+ if (!Array.isArray(value)) {
179
+ return [];
180
+ }
181
+ const items = [];
182
+ for (const entry of value) {
183
+ const record = entry;
184
+ const context = sanitizeContext(record === null || record === void 0 ? void 0 : record.context);
185
+ if (context !== null && typeof (record === null || record === void 0 ? void 0 : record.instruction) === 'string') {
186
+ items.push({
187
+ id: nextId(),
188
+ context,
189
+ instruction: record.instruction,
190
+ target: sanitizeTarget(record === null || record === void 0 ? void 0 : record.target)
191
+ });
192
+ }
193
+ }
194
+ return items;
195
+ }
196
+ /**
197
+ * The queue as a JSON value for the state database. Ids are session-scoped
198
+ * handles, so they are not persisted; {@link deserializeQueuedPrompts}
199
+ * assigns fresh ones on load.
200
+ */
201
+ export function serializeQueuedPrompts(items) {
202
+ return items.map(({ context, instruction, target }) => ({
203
+ context: { ...context },
204
+ instruction,
205
+ target: target === null ? null : { ...target }
206
+ }));
207
+ }
@@ -0,0 +1,76 @@
1
+ import type { TranslationBundle } from '@jupyterlab/translation';
2
+ import { ReactWidget } from '@jupyterlab/ui-components';
3
+ import type { CommandRegistry } from '@lumino/commands';
4
+ import type { Message } from '@lumino/messaging';
5
+ import type { IAgent } from '../launcher/agents';
6
+ import type { PromptQueue } from './queue';
7
+ import type { ISessionTarget } from './targetPicker';
8
+ /**
9
+ * The right-sidebar review panel for queued ask-agent prompts.
10
+ *
11
+ * Lists every queued comment — jump-to-code context line, snippet preview,
12
+ * editable instruction, its own destination picker, remove button — plus a
13
+ * send button that flushes the whole queue in one go: prompts sharing a
14
+ * destination are combined into one numbered message, and each destination
15
+ * gets its own delivery. The widget re-renders on queue changes itself; the
16
+ * plugin additionally ties it to the agent registry and terminal signals so
17
+ * the destination choices stay current.
18
+ */
19
+ export declare class AskAgentQueuePanel extends ReactWidget {
20
+ constructor(options: AskAgentQueuePanel.IOptions);
21
+ render(): JSX.Element;
22
+ /**
23
+ * Dispose on close (e.g. the close button when the panel has been dragged
24
+ * to the main area) rather than leaving a detached, reusable widget
25
+ * behind. The plugin recreates the panel in the side area on demand; the
26
+ * queue itself lives on in the model.
27
+ */
28
+ protected onCloseRequest(msg: Message): void;
29
+ private _onQueueChanged;
30
+ private _options;
31
+ }
32
+ /**
33
+ * A namespace for `AskAgentQueuePanel` statics.
34
+ */
35
+ export declare namespace AskAgentQueuePanel {
36
+ /**
37
+ * Construction options for {@link AskAgentQueuePanel}.
38
+ */
39
+ interface IOptions {
40
+ /**
41
+ * The queue the panel lists and edits.
42
+ */
43
+ queue: PromptQueue;
44
+ /**
45
+ * Command registry used to open queued locations in the editor.
46
+ */
47
+ commands: CommandRegistry;
48
+ /**
49
+ * Live reader of the prompt-capable agents (called on every render).
50
+ */
51
+ agents: () => IAgent[];
52
+ /**
53
+ * Live reader of the running agent terminals (called on every render).
54
+ */
55
+ targets: () => ISessionTarget[];
56
+ /**
57
+ * Live reader of whether a batch send is in flight (called on every
58
+ * render). While true the send and clear buttons are disabled, so one
59
+ * flush cannot be double-delivered.
60
+ */
61
+ sending: () => boolean;
62
+ /**
63
+ * Translation bundle for the panel's own labels.
64
+ */
65
+ trans: TranslationBundle;
66
+ /**
67
+ * Send every queued prompt to its own destination (the panel only calls
68
+ * this while all instructions and destinations are valid).
69
+ */
70
+ onSend: () => void;
71
+ /**
72
+ * Clear the queue (the plugin offers an undo toast).
73
+ */
74
+ onClear: () => void;
75
+ }
76
+ }
@@ -0,0 +1,188 @@
1
+ import { Notification } from '@jupyterlab/apputils';
2
+ import { PathExt } from '@jupyterlab/coreutils';
3
+ import { closeIcon, HTMLSelect, ReactWidget } from '@jupyterlab/ui-components';
4
+ import * as React from 'react';
5
+ import { contextSummary } from './popup';
6
+ import { serverPath } from './prompt';
7
+ /**
8
+ * Command the context buttons dispatch; keeps the panel decoupled from the plugin.
9
+ */
10
+ const HIGHLIGHT_LINES_COMMAND = 'xtralab:highlight-lines';
11
+ /**
12
+ * Textarea height that fits `text` without scrolling, within sane bounds:
13
+ * tall enough to look editable, short enough that one long comment cannot
14
+ * crowd out the rest of the queue.
15
+ */
16
+ function rowsFor(text) {
17
+ return Math.min(8, Math.max(2, text.split('\n').length));
18
+ }
19
+ /**
20
+ * A target as a `<select>` option value: `session:<name>` for a running
21
+ * agent terminal, `new:<agentId>` for a fresh terminal, `''` for a missing
22
+ * target (the placeholder option).
23
+ */
24
+ function encodeTarget(target) {
25
+ if (target === null) {
26
+ return '';
27
+ }
28
+ return target.kind === 'session'
29
+ ? `session:${target.name}`
30
+ : `new:${target.agentId}`;
31
+ }
32
+ function decodeTarget(value) {
33
+ if (value.startsWith('session:')) {
34
+ return { kind: 'session', name: value.slice('session:'.length) };
35
+ }
36
+ if (value.startsWith('new:')) {
37
+ return { kind: 'new', agentId: value.slice('new:'.length) };
38
+ }
39
+ return null;
40
+ }
41
+ /**
42
+ * Whether `target` can be sent to right now: its session is still running,
43
+ * or its agent still accepts a command-line prompt.
44
+ */
45
+ function targetIsLive(target, agents, targets) {
46
+ if (target === null) {
47
+ return false;
48
+ }
49
+ return target.kind === 'session'
50
+ ? targets.some(session => session.name === target.name)
51
+ : agents.some(agent => agent.id === target.agentId);
52
+ }
53
+ function QueuePanelComponent(props) {
54
+ const { queue, commands, agents: readAgents, targets: readTargets, sending: readSending, trans, onSend, onClear } = props;
55
+ // Snapshots for this render; the widget re-renders on every queue, agent
56
+ // registry and terminal change, so these stay current.
57
+ const items = queue.items;
58
+ const agents = readAgents();
59
+ const targets = readTargets();
60
+ const sending = readSending();
61
+ const complete = items.every(item => item.instruction.trim().length > 0);
62
+ const targeted = items.every(item => targetIsLive(item.target, agents, targets));
63
+ const canSend = items.length > 0 && complete && targeted && !sending;
64
+ const jumpTo = (item) => {
65
+ var _a;
66
+ const { context } = item;
67
+ const path = serverPath(context);
68
+ // The highlighter needs line numbers that index the working file:
69
+ // notebook lines are cell-relative (or raw-JSON offsets), and diff
70
+ // selections may come from another revision — just open the document
71
+ // in those cases.
72
+ const jump = context.cell === undefined &&
73
+ context.startLine !== undefined &&
74
+ context.linesInWorkingFile !== false &&
75
+ PathExt.extname(path) !== '.ipynb'
76
+ ? commands.execute(HIGHLIGHT_LINES_COMMAND, {
77
+ path,
78
+ line: context.startLine,
79
+ endLine: (_a = context.endLine) !== null && _a !== void 0 ? _a : context.startLine
80
+ })
81
+ : commands.execute('docmanager:open', { path });
82
+ jump.catch((error) => {
83
+ console.error('xtralab: failed to open the queued location', error);
84
+ Notification.error(trans.__('Could not open %1', path), {
85
+ autoClose: 3000
86
+ });
87
+ });
88
+ };
89
+ if (items.length === 0) {
90
+ return (React.createElement("div", { className: "jp-xtralab-AskAgentQueue-body" },
91
+ React.createElement("div", { className: "jp-xtralab-AskAgentQueue-blank" }, trans.__('No queued prompts. Select code in an editor, notebook or git diff and choose “Queue” in the ask-agent popup; each prompt keeps its own destination and everything is sent in one go.'))));
92
+ }
93
+ return (React.createElement("div", { className: "jp-xtralab-AskAgentQueue-body" },
94
+ React.createElement("ul", { className: "jp-xtralab-AskAgentQueue-list" }, items.map(item => {
95
+ var _a, _b, _c, _d;
96
+ const summary = contextSummary(item.context, trans);
97
+ const missing = item.instruction.trim().length === 0;
98
+ const target = item.target;
99
+ const live = targetIsLive(target, agents, targets);
100
+ // Say what sending will do for this prompt — paste into a running
101
+ // terminal, or start a fresh one — and badge it with the agent's
102
+ // icon, so a mixed queue can be scanned without opening dropdowns.
103
+ let icon = null;
104
+ let targetTitle = trans.__('The destination picked for this prompt is gone — choose another');
105
+ if (live && target !== null) {
106
+ if (target.kind === 'session') {
107
+ icon =
108
+ (_b = (_a = targets.find(session => session.name === target.name)) === null || _a === void 0 ? void 0 : _a.icon) !== null && _b !== void 0 ? _b : null;
109
+ targetTitle = trans.__('Sends into the agent running in terminal %1', target.name);
110
+ }
111
+ else {
112
+ const agent = agents.find(entry => entry.id === target.agentId);
113
+ icon = (_c = agent === null || agent === void 0 ? void 0 : agent.icon) !== null && _c !== void 0 ? _c : null;
114
+ targetTitle = trans.__('Starts %1 in a new terminal', (_d = agent === null || agent === void 0 ? void 0 : agent.label) !== null && _d !== void 0 ? _d : target.agentId);
115
+ }
116
+ }
117
+ return (React.createElement("li", { className: "jp-xtralab-AskAgentQueue-item", key: item.id },
118
+ React.createElement("div", { className: "jp-xtralab-AskAgentQueue-itemHeader" },
119
+ React.createElement("button", { type: "button", className: "jp-xtralab-AskAgentQueue-itemContext", title: trans.__('Open %1 in the editor', serverPath(item.context)), onClick: () => jumpTo(item) }, summary),
120
+ React.createElement("button", { type: "button", className: "jp-xtralab-AskAgentQueue-itemRemove", title: trans.__('Remove from the queue'), "aria-label": trans.__('Remove from the queue'), onClick: () => queue.remove(item.id) },
121
+ React.createElement(closeIcon.react, { tag: "span", className: "jp-xtralab-AskAgentQueue-itemRemoveIcon" }))),
122
+ item.context.text.length > 0 && (React.createElement("pre", { className: "jp-xtralab-AskAgentQueue-snippet" }, item.context.text)),
123
+ React.createElement("textarea", { className: "jp-xtralab-AskAgentQueue-instruction", rows: rowsFor(item.instruction), spellCheck: false, "aria-invalid": missing, "aria-label": trans.__('Instruction about %1', summary), placeholder: trans.__('Describe the change…'), defaultValue: item.instruction, onChange: event => queue.updateInstruction(item.id, event.target.value) }),
124
+ React.createElement("div", { className: "jp-xtralab-AskAgentQueue-itemTargetRow" },
125
+ React.createElement("span", { className: "jp-xtralab-AskAgentQueue-itemTargetIcon", "aria-hidden": "true" }, icon !== null && React.createElement(icon.react, { tag: "span" })),
126
+ React.createElement(HTMLSelect, { className: "jp-xtralab-AskAgentQueue-itemTarget", "aria-invalid": !live, "aria-label": trans.__('Send to'), title: targetTitle, value: live ? encodeTarget(target) : '', onChange: event => {
127
+ const picked = decodeTarget(event.target.value);
128
+ if (picked !== null) {
129
+ queue.updateTarget(item.id, picked);
130
+ }
131
+ } },
132
+ !live && (React.createElement("option", { value: "", disabled: true }, trans.__('Choose where to send…'))),
133
+ targets.length > 0 && (React.createElement("optgroup", { label: trans.__('Running agents') }, targets.map(session => (React.createElement("option", { key: session.name, value: `session:${session.name}` }, trans.__('Terminal %1 · %2', session.name, session.label)))))),
134
+ agents.length > 0 && (React.createElement("optgroup", { label: trans.__('New terminal') }, agents.map(agent => (React.createElement("option", { key: agent.id, value: `new:${agent.id}` }, trans.__('New terminal · %1', agent.label))))))))));
135
+ })),
136
+ React.createElement("div", { className: "jp-xtralab-AskAgentQueue-footer" },
137
+ agents.length === 0 && targets.length === 0 && (React.createElement("div", { className: "jp-xtralab-AskAgentQueue-noTargets" }, trans.__('No agent accepts an initial prompt. Configure agents in the launcher settings.'))),
138
+ React.createElement("div", { className: "jp-xtralab-AskAgentQueue-actions" },
139
+ React.createElement("button", { type: "button", className: "jp-xtralab-AskAgentQueue-clear", disabled: sending, title: trans.__('Remove every queued prompt'), onClick: onClear }, trans.__('Clear')),
140
+ React.createElement("button", { type: "button", className: "jp-xtralab-AskAgentQueue-send", disabled: !canSend, title: sending
141
+ ? trans.__('Sending…')
142
+ : !complete
143
+ ? trans.__('Every queued prompt needs an instruction')
144
+ : !targeted
145
+ ? trans.__('Every queued prompt needs a destination')
146
+ : trans.__('Send each queued prompt to its chosen destination'), onClick: () => {
147
+ if (canSend) {
148
+ onSend();
149
+ }
150
+ } }, sending
151
+ ? trans.__('Sending…')
152
+ : trans._n('Send %1 prompt', 'Send %1 prompts', items.length))))));
153
+ }
154
+ /**
155
+ * The right-sidebar review panel for queued ask-agent prompts.
156
+ *
157
+ * Lists every queued comment — jump-to-code context line, snippet preview,
158
+ * editable instruction, its own destination picker, remove button — plus a
159
+ * send button that flushes the whole queue in one go: prompts sharing a
160
+ * destination are combined into one numbered message, and each destination
161
+ * gets its own delivery. The widget re-renders on queue changes itself; the
162
+ * plugin additionally ties it to the agent registry and terminal signals so
163
+ * the destination choices stay current.
164
+ */
165
+ export class AskAgentQueuePanel extends ReactWidget {
166
+ constructor(options) {
167
+ super();
168
+ this._options = options;
169
+ this.addClass('jp-xtralab-AskAgentQueue');
170
+ options.queue.changed.connect(this._onQueueChanged, this);
171
+ }
172
+ render() {
173
+ return React.createElement(QueuePanelComponent, { ...this._options });
174
+ }
175
+ /**
176
+ * Dispose on close (e.g. the close button when the panel has been dragged
177
+ * to the main area) rather than leaving a detached, reusable widget
178
+ * behind. The plugin recreates the panel in the side area on demand; the
179
+ * queue itself lives on in the model.
180
+ */
181
+ onCloseRequest(msg) {
182
+ super.onCloseRequest(msg);
183
+ this.dispose();
184
+ }
185
+ _onQueueChanged() {
186
+ this.update();
187
+ }
188
+ }
@@ -0,0 +1,58 @@
1
+ import type { TranslationBundle } from '@jupyterlab/translation';
2
+ import { LabIcon } from '@jupyterlab/ui-components';
3
+ import type { IAgent } from '../launcher/agents';
4
+ /**
5
+ * A running agent terminal offered as a prompt target: the session data
6
+ * from `IAgentTerminals` plus the matching agent's icon, resolved by the
7
+ * plugin so these components stay presentation-only.
8
+ */
9
+ export interface ISessionTarget {
10
+ /**
11
+ * The terminal session name.
12
+ */
13
+ name: string;
14
+ /**
15
+ * The session's display label (usually the agent's name).
16
+ */
17
+ label: string;
18
+ /**
19
+ * The agent's latest activity line, when one is available.
20
+ */
21
+ activity: string | null;
22
+ /**
23
+ * The running agent's icon.
24
+ */
25
+ icon: LabIcon;
26
+ }
27
+ /**
28
+ * The chip row picking where a prompt goes: "New terminal" plus one chip
29
+ * per running agent session. Renders nothing while no agent session is
30
+ * running (a new terminal is then the only possibility and the row would
31
+ * be noise).
32
+ */
33
+ export declare function TargetChips(props: {
34
+ /**
35
+ * Agents that could start in a new terminal; gates the "New terminal" chip.
36
+ */
37
+ agents: IAgent[];
38
+ /**
39
+ * The running agent sessions.
40
+ */
41
+ targets: ISessionTarget[];
42
+ /**
43
+ * The selected session name, or `null` for a new terminal.
44
+ */
45
+ targetName: string | null;
46
+ trans: TranslationBundle;
47
+ onSelect: (targetName: string | null) => void;
48
+ }): JSX.Element | null;
49
+ /**
50
+ * The icon radiogroup picking which agent a new terminal starts with.
51
+ * Renders nothing when no agent accepts an initial prompt.
52
+ */
53
+ export declare function AgentChoices(props: {
54
+ agents: IAgent[];
55
+ agentId: string;
56
+ trans: TranslationBundle;
57
+ onSelect: (agentId: string) => void;
58
+ }): JSX.Element | null;
@@ -0,0 +1,74 @@
1
+ import { addIcon } from '@jupyterlab/ui-components';
2
+ import * as React from 'react';
3
+ /**
4
+ * Keyboard support shared by the two radiogroups below, following the ARIA
5
+ * radio-group pattern: the group is one tab stop (only the checked radio is
6
+ * tabbable) and the arrow keys move the selection to the neighbouring
7
+ * radio, wrapping around. Selection follows focus, so the handler clicks
8
+ * the neighbour and the re-render moves the roving tabindex along.
9
+ */
10
+ function onRadioGroupKeyDown(event) {
11
+ let delta;
12
+ switch (event.key) {
13
+ case 'ArrowRight':
14
+ case 'ArrowDown':
15
+ delta = 1;
16
+ break;
17
+ case 'ArrowLeft':
18
+ case 'ArrowUp':
19
+ delta = -1;
20
+ break;
21
+ default:
22
+ return;
23
+ }
24
+ const radios = Array.from(event.currentTarget.querySelectorAll('[role="radio"]'));
25
+ const index = radios.findIndex(radio => radio === event.target);
26
+ if (index === -1) {
27
+ return;
28
+ }
29
+ event.preventDefault();
30
+ const next = radios[(index + delta + radios.length) % radios.length];
31
+ next.focus();
32
+ next.click();
33
+ }
34
+ /**
35
+ * The chip row picking where a prompt goes: "New terminal" plus one chip
36
+ * per running agent session. Renders nothing while no agent session is
37
+ * running (a new terminal is then the only possibility and the row would
38
+ * be noise).
39
+ */
40
+ export function TargetChips(props) {
41
+ const { agents, targets, targetName, trans, onSelect } = props;
42
+ if (targets.length === 0) {
43
+ return null;
44
+ }
45
+ return (React.createElement("div", { className: "jp-xtralab-AskAgent-targets", role: "radiogroup", "aria-label": trans.__('Send to'), onKeyDown: onRadioGroupKeyDown },
46
+ agents.length > 0 && (React.createElement("button", { type: "button", role: "radio", "aria-checked": targetName === null, tabIndex: targetName === null ? 0 : -1, title: trans.__('Start the agent in a new terminal'), className: 'jp-xtralab-AskAgent-targetButton' +
47
+ (targetName === null ? ' jp-mod-selected' : ''),
48
+ // Keep focus where the user is typing while picking a target.
49
+ onMouseDown: event => event.preventDefault(), onClick: () => onSelect(null) },
50
+ React.createElement(addIcon.react, { tag: "span", className: "jp-xtralab-AskAgent-targetIcon" }),
51
+ React.createElement("span", { className: "jp-xtralab-AskAgent-targetLabel" }, trans.__('New terminal')))),
52
+ targets.map(target => (React.createElement("button", { key: target.name, type: "button", role: "radio", "aria-checked": target.name === targetName, tabIndex: target.name === targetName ? 0 : -1, title: target.activity
53
+ ? trans.__('%1 · %2 — %3', target.label, target.name, target.activity)
54
+ : trans.__('%1 · %2', target.label, target.name), className: 'jp-xtralab-AskAgent-targetButton' +
55
+ (target.name === targetName ? ' jp-mod-selected' : ''), onMouseDown: event => event.preventDefault(), onClick: () => onSelect(target.name) },
56
+ React.createElement(target.icon.react, { tag: "span", className: "jp-xtralab-AskAgent-targetIcon" }),
57
+ React.createElement("span", { className: "jp-xtralab-AskAgent-targetLabel" }, target.label),
58
+ React.createElement("span", { className: "jp-xtralab-AskAgent-targetName" }, target.name))))));
59
+ }
60
+ /**
61
+ * The icon radiogroup picking which agent a new terminal starts with.
62
+ * Renders nothing when no agent accepts an initial prompt.
63
+ */
64
+ export function AgentChoices(props) {
65
+ const { agents, agentId, trans, onSelect } = props;
66
+ if (agents.length === 0) {
67
+ return null;
68
+ }
69
+ return (React.createElement("div", { className: "jp-xtralab-AskAgent-agents", role: "radiogroup", "aria-label": trans.__('Agent'), onKeyDown: onRadioGroupKeyDown }, agents.map(agent => (React.createElement("button", { key: agent.id, type: "button", role: "radio", "aria-checked": agent.id === agentId, tabIndex: agent.id === agentId ? 0 : -1, title: agent.label, "aria-label": agent.label, className: 'jp-xtralab-AskAgent-agentButton' +
70
+ (agent.id === agentId ? ' jp-mod-selected' : ''),
71
+ // Keep focus where the user is typing while picking an agent.
72
+ onMouseDown: event => event.preventDefault(), onClick: () => onSelect(agent.id) },
73
+ React.createElement(agent.icon.react, { tag: "span", className: "jp-xtralab-AskAgent-agentIcon" }))))));
74
+ }