jupyterlab_ai_code_assistants_extension 1.2.35 → 1.2.41
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 +1 -1
- package/lib/core/panel.d.ts +1 -21
- package/lib/core/panel.js +20 -59
- package/lib/core/terminals.js +2 -2
- package/lib/core/types.d.ts +2 -1
- package/lib/index.js +10 -22
- package/lib/providers/claude.js +3 -3
- package/package.json +1 -1
- package/src/__tests__/index.spec.ts +34 -0
- package/src/__tests__/panel.spec.ts +7 -5
- package/src/core/panel.ts +22 -64
- package/src/core/terminals.ts +2 -2
- package/src/core/types.ts +2 -1
- package/src/index.ts +8 -24
- package/src/providers/claude.ts +3 -3
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ Chat-panel extensions re-implement the agent loop and trail the real tool. This
|
|
|
41
41
|
- **Favorites** - star projects you keep coming back to via the right-click menu; favourites from the standalone extensions are migrated on first run
|
|
42
42
|
- **Remove and clean up** - drop a project's history or a project's extra parallel sessions from the right-click menu, confirmation dialog first; removed files honour JupyterLab's "move files to trash" setting
|
|
43
43
|
- **Activity at a glance** - each row shows its last activity in an aligned column; rows active within the last minute light up, rows idle for over a week dim, and rows with parallel conversations show a branch icon with the count
|
|
44
|
-
- **Remote control indicator and background agents** (Claude) - a green dot marks sessions actively under remote control, and a conversation held by a running background agent shows a `bg` chip; clicking
|
|
44
|
+
- **Remote control indicator and background agents** (Claude) - a green dot marks sessions actively under remote control, and a conversation held by a running background agent shows a `bg` chip; clicking stops the agent and resumes the same conversation in your terminal, so it becomes an ordinary interactive session
|
|
45
45
|
- **Search** - fuzzy filter per panel, toggled by the funnel button
|
|
46
46
|
- **Presentation modes** - label rows by session name or by path relative to the JupyterLab root
|
|
47
47
|
- **Conflict-safe upgrade** - if a retired standalone extension is still installed, its panel wins and this extension stands down for that assistant instead of showing a duplicate
|
package/lib/core/panel.d.ts
CHANGED
|
@@ -252,24 +252,6 @@ export declare class AssistantSessionsPanel extends Widget {
|
|
|
252
252
|
* that build a byte-identical launch, and the unmarked one is the default
|
|
253
253
|
* the user clicks. */
|
|
254
254
|
private _resolvedVariant;
|
|
255
|
-
/** The same mode as it applies to RESUME - of the active row by default, or
|
|
256
|
-
* of whichever conversation is named.
|
|
257
|
-
*
|
|
258
|
-
* An attach is not a launch: the server returns the argv before the flag is
|
|
259
|
-
* appended, deliberately, so promising the mode on a conversation a live
|
|
260
|
-
* worker already holds would name something the assistant never receives.
|
|
261
|
-
* That is true of resuming and of nothing else, so it is tested HERE rather
|
|
262
|
-
* than in `_resolvedVariant` - which is read by the `+` button and both
|
|
263
|
-
* new-session and branch items, none of which attach. Testing it there let
|
|
264
|
-
* one right-click on a background-agent row strip the mode's name off every
|
|
265
|
-
* one of those surfaces panel-wide, while their launches went on carrying it
|
|
266
|
-
* (DEF-37).
|
|
267
|
-
*
|
|
268
|
-
* The argument is what lets a surface that describes SOMEONE ELSE'S resume -
|
|
269
|
-
* a row tooltip built during a render, a branch item in the submenu - ask the
|
|
270
|
-
* question about that conversation rather than about the active row.
|
|
271
|
-
*/
|
|
272
|
-
private _resumeVariant;
|
|
273
255
|
/** The variant suffix a plain item wears while that mode is in force. */
|
|
274
256
|
private _variantSuffix;
|
|
275
257
|
/** Whether a forced variant would build the launch the plain item already
|
|
@@ -311,9 +293,7 @@ export declare class AssistantSessionsPanel extends Widget {
|
|
|
311
293
|
private _openManagePopup;
|
|
312
294
|
/** Tooltip for one Open button in the Manage Sessions popup. Same rule as the
|
|
313
295
|
* Open Branched Conversation submenu: the button launches a terminal, so it
|
|
314
|
-
* names the mode that launch carries
|
|
315
|
-
* is attached to and carries none. The row's own conversation is not in
|
|
316
|
-
* `_lastBranches`, so it answers from the session. */
|
|
296
|
+
* names the mode that launch carries. */
|
|
317
297
|
private _openBranchTitle;
|
|
318
298
|
/** The live-worker chip for a branch row, when the provider has workers at
|
|
319
299
|
* all. Returning null lets the popup fall back to its fork marker. */
|
package/lib/core/panel.js
CHANGED
|
@@ -1593,9 +1593,9 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
1593
1593
|
badge.appendChild(document.createTextNode(String(session.extra_sessions + 1)));
|
|
1594
1594
|
name.appendChild(badge);
|
|
1595
1595
|
}
|
|
1596
|
-
// A live worker owns this conversation, so a click
|
|
1597
|
-
// resuming
|
|
1598
|
-
//
|
|
1596
|
+
// A live worker owns this conversation, so a click stops it before
|
|
1597
|
+
// resuming. Marked so the difference is visible before the click, not a
|
|
1598
|
+
// surprise after it. No own title: a child title shadows the
|
|
1599
1599
|
// row's, which would hide the path behind a two-character chip.
|
|
1600
1600
|
if (this._descriptor.hasBgAgents && session.bg_id) {
|
|
1601
1601
|
const bg = document.createElement('span');
|
|
@@ -1767,15 +1767,10 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
1767
1767
|
lines.push('Status: active');
|
|
1768
1768
|
}
|
|
1769
1769
|
if (this._descriptor.hasBgAgents && s.bg_id) {
|
|
1770
|
-
lines.push(`Background agent: ${s.bg_id} (click
|
|
1770
|
+
lines.push(`Background agent: ${s.bg_id} (click stops it and resumes here)`);
|
|
1771
1771
|
}
|
|
1772
1772
|
lines.push(...((_c = (_b = (_a = this._hooks).tooltipLines) === null || _b === void 0 ? void 0 : _b.call(_a, s)) !== null && _c !== void 0 ? _c : []));
|
|
1773
|
-
|
|
1774
|
-
// the line above may have just said the click joins a live worker, and an
|
|
1775
|
-
// attach is issued before the mode is appended, so `_resolvedVariant`
|
|
1776
|
-
// promised a flag the server discards - one tooltip naming both, a line
|
|
1777
|
-
// apart (DEF-46).
|
|
1778
|
-
const variant = this._resumeVariant(s);
|
|
1773
|
+
const variant = this._resolvedVariant();
|
|
1779
1774
|
if (variant) {
|
|
1780
1775
|
// The row click launches with this, and the menu is the only other place
|
|
1781
1776
|
// that says so.
|
|
@@ -1891,27 +1886,6 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
1891
1886
|
const resolved = resolvedLaunchModeEntry(this._descriptor.launchModes, this._modes);
|
|
1892
1887
|
return resolved;
|
|
1893
1888
|
}
|
|
1894
|
-
/** The same mode as it applies to RESUME - of the active row by default, or
|
|
1895
|
-
* of whichever conversation is named.
|
|
1896
|
-
*
|
|
1897
|
-
* An attach is not a launch: the server returns the argv before the flag is
|
|
1898
|
-
* appended, deliberately, so promising the mode on a conversation a live
|
|
1899
|
-
* worker already holds would name something the assistant never receives.
|
|
1900
|
-
* That is true of resuming and of nothing else, so it is tested HERE rather
|
|
1901
|
-
* than in `_resolvedVariant` - which is read by the `+` button and both
|
|
1902
|
-
* new-session and branch items, none of which attach. Testing it there let
|
|
1903
|
-
* one right-click on a background-agent row strip the mode's name off every
|
|
1904
|
-
* one of those surfaces panel-wide, while their launches went on carrying it
|
|
1905
|
-
* (DEF-37).
|
|
1906
|
-
*
|
|
1907
|
-
* The argument is what lets a surface that describes SOMEONE ELSE'S resume -
|
|
1908
|
-
* a row tooltip built during a render, a branch item in the submenu - ask the
|
|
1909
|
-
* question about that conversation rather than about the active row.
|
|
1910
|
-
*/
|
|
1911
|
-
_resumeVariant(target = this._activeSession) {
|
|
1912
|
-
const joining = this._descriptor.hasBgAgents && !!(target === null || target === void 0 ? void 0 : target.bg_id);
|
|
1913
|
-
return joining ? null : this._resolvedVariant();
|
|
1914
|
-
}
|
|
1915
1889
|
/** The variant suffix a plain item wears while that mode is in force. */
|
|
1916
1890
|
_variantSuffix() {
|
|
1917
1891
|
const resolved = this._resolvedVariant();
|
|
@@ -1951,16 +1925,15 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
1951
1925
|
}
|
|
1952
1926
|
});
|
|
1953
1927
|
this._commands.addCommand(this._cmd('resume'), {
|
|
1954
|
-
//
|
|
1955
|
-
//
|
|
1956
|
-
// gets.
|
|
1928
|
+
// Resuming a conversation a live worker holds stops that worker first,
|
|
1929
|
+
// so a provider that has workers says which of the two this row gets.
|
|
1957
1930
|
label: () => {
|
|
1958
1931
|
var _a, _b, _c;
|
|
1959
|
-
const variant = this.
|
|
1932
|
+
const variant = this._resolvedVariant();
|
|
1960
1933
|
const verb = (_c = (_b = (_a = this._hooks).resumeLabel) === null || _b === void 0 ? void 0 : _b.call(_a, active())) !== null && _c !== void 0 ? _c : 'Resume';
|
|
1961
1934
|
return variant ? `${verb} (${variant.label})` : verb;
|
|
1962
1935
|
},
|
|
1963
|
-
icon: () => { var _a; return (((_a = this.
|
|
1936
|
+
icon: () => { var _a; return (((_a = this._resolvedVariant()) === null || _a === void 0 ? void 0 : _a.unsafe) ? shieldIcon : undefined); },
|
|
1964
1937
|
execute: () => {
|
|
1965
1938
|
const s = active();
|
|
1966
1939
|
if (s) {
|
|
@@ -1970,15 +1943,13 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
1970
1943
|
});
|
|
1971
1944
|
for (const mode of this._variantModes) {
|
|
1972
1945
|
this._commands.addCommand(this._cmd(`resume-${mode.id}`), {
|
|
1973
|
-
|
|
1946
|
+
// The same verb as the plain item, so a row a live worker holds says
|
|
1947
|
+
// here too that the worker is stopped first.
|
|
1948
|
+
label: () => { var _a, _b, _c; return `${(_c = (_b = (_a = this._hooks).resumeLabel) === null || _b === void 0 ? void 0 : _b.call(_a, active())) !== null && _c !== void 0 ? _c : 'Resume'} (${mode.menuLabel})`; },
|
|
1974
1949
|
icon: shieldIcon,
|
|
1975
1950
|
// Absent while the plain item already resolves to this same mode - it
|
|
1976
1951
|
// would be a second entry building an identical launch.
|
|
1977
1952
|
isVisible: () => !this._buildsSameLaunch(mode),
|
|
1978
|
-
// A conversation a live worker already holds was started in whatever
|
|
1979
|
-
// mode that worker has; joining it cannot change that, so the variant
|
|
1980
|
-
// is disabled rather than silently joining in the wrong mode.
|
|
1981
|
-
isEnabled: () => { var _a; return !(this._descriptor.hasBgAgents && ((_a = active()) === null || _a === void 0 ? void 0 : _a.bg_id)); },
|
|
1982
1953
|
execute: () => {
|
|
1983
1954
|
const s = active();
|
|
1984
1955
|
if (s) {
|
|
@@ -2127,13 +2098,11 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2127
2098
|
this._commands.addCommand(this._cmd('open-branch'), {
|
|
2128
2099
|
// Names the mode the launch carries, like Resume, `+` and Branch Session
|
|
2129
2100
|
// do - this item opens a terminal, so the mode in force applies to it and
|
|
2130
|
-
// this was the one launch surface that stayed silent about it.
|
|
2131
|
-
// aware per conversation: the submenu passes `bg`, since a branch a live
|
|
2132
|
-
// worker holds is attached to and takes no mode (DEF-46's rule).
|
|
2101
|
+
// this was the one launch surface that stayed silent about it.
|
|
2133
2102
|
label: args => {
|
|
2134
2103
|
var _a;
|
|
2135
2104
|
const base = String((_a = args.label) !== null && _a !== void 0 ? _a : '');
|
|
2136
|
-
const variant = this.
|
|
2105
|
+
const variant = this._resolvedVariant();
|
|
2137
2106
|
return variant ? `${base} (${variant.label})` : base;
|
|
2138
2107
|
},
|
|
2139
2108
|
// Intent glyph, not row state - see switch-branch above.
|
|
@@ -2436,10 +2405,7 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2436
2405
|
command: this._cmd('open-branch'),
|
|
2437
2406
|
args: {
|
|
2438
2407
|
session_id: b.session_id,
|
|
2439
|
-
label: this._branchMenuLabel(b)
|
|
2440
|
-
// The label reads this to tell an attach from a launch; the
|
|
2441
|
-
// provider's own worker capability is tested where it is answered.
|
|
2442
|
-
...(b.bg_id ? { bg: b.bg_id } : {})
|
|
2408
|
+
label: this._branchMenuLabel(b)
|
|
2443
2409
|
}
|
|
2444
2410
|
});
|
|
2445
2411
|
}
|
|
@@ -2463,7 +2429,7 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2463
2429
|
branchName: b => this._branchDisplayName(b),
|
|
2464
2430
|
formatTime: ms => this._formatRelativeTime(ms),
|
|
2465
2431
|
branchBadge: b => this._bgBadgeFor(b),
|
|
2466
|
-
openTitle:
|
|
2432
|
+
openTitle: () => this._openBranchTitle(session),
|
|
2467
2433
|
onSwitch: id => void this._switchBranch(session, id),
|
|
2468
2434
|
onOpen: id => void this._openSession(session, undefined, id),
|
|
2469
2435
|
onDelete: ids => this._deleteBranches(session, ids),
|
|
@@ -2475,21 +2441,16 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2475
2441
|
}
|
|
2476
2442
|
/** Tooltip for one Open button in the Manage Sessions popup. Same rule as the
|
|
2477
2443
|
* Open Branched Conversation submenu: the button launches a terminal, so it
|
|
2478
|
-
* names the mode that launch carries
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
_openBranchTitle(session, sessionId) {
|
|
2482
|
-
var _a, _b, _c, _d;
|
|
2444
|
+
* names the mode that launch carries. */
|
|
2445
|
+
_openBranchTitle(session) {
|
|
2446
|
+
var _a, _b, _c;
|
|
2483
2447
|
// One terminal serves a project-scoped assistant, so the button focuses
|
|
2484
2448
|
// or starts it, under the provider's own verb for that.
|
|
2485
2449
|
if (this._descriptor.terminalScope === 'project') {
|
|
2486
2450
|
return ((_c = (_b = (_a = this._hooks).resumeLabel) === null || _b === void 0 ? void 0 : _b.call(_a, session)) !== null && _c !== void 0 ? _c : "Open the project's terminal");
|
|
2487
2451
|
}
|
|
2488
2452
|
const base = 'Open this conversation in its own terminal';
|
|
2489
|
-
const
|
|
2490
|
-
? session
|
|
2491
|
-
: ((_d = this._lastBranches.find(b => b.session_id === sessionId)) !== null && _d !== void 0 ? _d : null);
|
|
2492
|
-
const variant = this._resumeVariant(target);
|
|
2453
|
+
const variant = this._resolvedVariant();
|
|
2493
2454
|
return variant ? `${base} (${variant.label})` : base;
|
|
2494
2455
|
}
|
|
2495
2456
|
/** The live-worker chip for a branch row, when the provider has workers at
|
package/lib/core/terminals.js
CHANGED
|
@@ -41,8 +41,8 @@ function warnLegacyCompanion() {
|
|
|
41
41
|
// And said where the user is looking. The symptom is tabs that are simply
|
|
42
42
|
// plain, which reads as a feature that was never on rather than as something
|
|
43
43
|
// to fix, and a console line only reaches someone already suspecting the
|
|
44
|
-
// cause. The
|
|
45
|
-
//
|
|
44
|
+
// cause. The retired-extension notice in index.ts pairs its console line
|
|
45
|
+
// with a toast the same way. It does not
|
|
46
46
|
// close on its own, because the condition does not go away on its own.
|
|
47
47
|
Notification.warning(message, { autoClose: false });
|
|
48
48
|
}
|
package/lib/core/types.d.ts
CHANGED
|
@@ -309,7 +309,8 @@ export interface ITabColourEvents {
|
|
|
309
309
|
* shared panel. */
|
|
310
310
|
export interface IProviderHooks {
|
|
311
311
|
/** Label for the primary open action, when the provider distinguishes verbs
|
|
312
|
-
* (
|
|
312
|
+
* (stopping a live worker before resuming is not the same as resuming a
|
|
313
|
+
* dormant store entry). */
|
|
313
314
|
resumeLabel?: (session: ISession | null) => string;
|
|
314
315
|
/** Extra lines for the row tooltip. */
|
|
315
316
|
tooltipLines?: (session: ISession) => string[];
|
package/lib/index.js
CHANGED
|
@@ -242,7 +242,7 @@ const plugin = {
|
|
|
242
242
|
/** Bring the docked panels in line with settings and CLI availability.
|
|
243
243
|
* Both must hold for a panel to be present. */
|
|
244
244
|
const reconcile = () => {
|
|
245
|
-
var _a, _b, _c
|
|
245
|
+
var _a, _b, _c;
|
|
246
246
|
for (const descriptor of registry.descriptors) {
|
|
247
247
|
const id = descriptor.id;
|
|
248
248
|
if (suppressed.has(id)) {
|
|
@@ -258,38 +258,26 @@ const plugin = {
|
|
|
258
258
|
// the server's word on each binary is final.
|
|
259
259
|
const roster = status;
|
|
260
260
|
const available = roster === null ? true : probe !== null && probe.available !== false;
|
|
261
|
-
// Enabled but binary absent
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
// Only when the probe SUCCEEDED and this provider is
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
// assistant installed would otherwise raise three toasts about assistants
|
|
268
|
-
// the user has never heard of. The console line stays for that case.
|
|
269
|
-
const chosen = (settings === null || settings === void 0 ? void 0 : settings.get(`providers.${id}.enabled`).user) !== undefined;
|
|
261
|
+
// Enabled but binary absent is logged to the console, never announced:
|
|
262
|
+
// enabled means the extension supports the assistant, not that it is
|
|
263
|
+
// installed, so a toast would name a tool the user never installed
|
|
264
|
+
// (DEF-PANE-277). Only when the probe SUCCEEDED and this provider is
|
|
265
|
+
// the one missing - blaming PATH for a server the frontend never
|
|
266
|
+
// reached names the wrong cause. Once per id, not per reconcile.
|
|
270
267
|
if (probe !== null &&
|
|
271
268
|
probe.available === false &&
|
|
272
269
|
enabled &&
|
|
273
|
-
chosen &&
|
|
274
270
|
!warnedUnavailable.has(id)) {
|
|
275
271
|
warnedUnavailable.add(id);
|
|
276
272
|
const binary = (_b = (_a = registry.get(id)) === null || _a === void 0 ? void 0 : _a.descriptor.cliBinary) !== null && _b !== void 0 ? _b : id;
|
|
277
|
-
const label = (_d = (_c = registry.get(id)) === null || _c === void 0 ? void 0 : _c.descriptor.label) !== null && _d !== void 0 ? _d : id;
|
|
278
273
|
console.info(`${LOG_PREFIX} "${id}" is enabled but its \`${binary}\` binary was not found on the server's PATH; panel not shown.`);
|
|
279
|
-
// And say it where the user is looking. A console line satisfies the
|
|
280
|
-
// comment above only for someone who already suspects the cause; the
|
|
281
|
-
// symptom is a panel that never appears, which reads as a broken
|
|
282
|
-
// install rather than a PATH difference between the Jupyter server
|
|
283
|
-
// and the user's shell. The retired-extension case next door already
|
|
284
|
-
// gets a notification, so the COMMON failure was the quiet one.
|
|
285
|
-
Notification.warning(`${label} is enabled but \`${binary}\` was not found on the Jupyter server's PATH, so its panel is not shown.`, { autoClose: 8000 });
|
|
286
274
|
}
|
|
287
275
|
// The latch is per ABSENCE, not per page load. Once the server reports
|
|
288
|
-
// the binary present again, a later disappearance gets its own
|
|
276
|
+
// the binary present again, a later disappearance gets its own line:
|
|
289
277
|
// without this, one transient absence - a CLI upgrade unlinking the
|
|
290
278
|
// binary for a moment, or an obsolete roster from an overlapping probe -
|
|
291
279
|
// permanently suppresses the real one (DEF-125). It cannot make the
|
|
292
|
-
//
|
|
280
|
+
// line repeat while the state is unchanged, which is what the
|
|
293
281
|
// once-per-id latch above exists to prevent: the two conditions are
|
|
294
282
|
// mutually exclusive.
|
|
295
283
|
if (probe !== null && probe.available !== false) {
|
|
@@ -302,7 +290,7 @@ const plugin = {
|
|
|
302
290
|
// A panel docked before the first roster was built without the
|
|
303
291
|
// server root, and cannot resolve paths until it has one.
|
|
304
292
|
if (roster !== null) {
|
|
305
|
-
panel.setRoot((
|
|
293
|
+
panel.setRoot((_c = roster.root_dir) !== null && _c !== void 0 ? _c : '', roster.delete_to_trash !== false);
|
|
306
294
|
}
|
|
307
295
|
}
|
|
308
296
|
else {
|
package/lib/providers/claude.js
CHANGED
|
@@ -70,7 +70,7 @@ export const descriptor = {
|
|
|
70
70
|
legacyPluginId: 'jupyterlab_claude_code_extension'
|
|
71
71
|
};
|
|
72
72
|
export const hooks = {
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
resumeLabel: (session) => (session === null || session === void 0 ? void 0 : session.bg_id) ? '
|
|
73
|
+
// Opening a conversation a live agent holds stops the agent first, so the
|
|
74
|
+
// item says so before the click (DEF-PROV-272).
|
|
75
|
+
resumeLabel: (session) => (session === null || session === void 0 ? void 0 : session.bg_id) ? 'Stop Background Agent and Resume' : 'Resume'
|
|
76
76
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab_ai_code_assistants_extension",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.41",
|
|
4
4
|
"description": "A full launcher and manager for every AI code assistant in JupyterLab - Claude Code, Codex, Kimi, Gemini and DeepSeek. Start, resume, fork, switch, and clean up CLI sessions from a side panel per assistant - one click lands you in the right terminal with the assistant already running, no duplicate tabs, no UUID hunting.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -500,6 +500,40 @@ describe('ACC-PROV - the activation contract', () => {
|
|
|
500
500
|
live.docked.forEach(widget => widget.dispose());
|
|
501
501
|
});
|
|
502
502
|
|
|
503
|
+
it('DEF-PANE-277 - an enabled assistant whose binary is absent gets no panel and no toast', async () => {
|
|
504
|
+
const toasted = jest
|
|
505
|
+
.spyOn(Notification, 'warning')
|
|
506
|
+
.mockImplementation(() => '');
|
|
507
|
+
const logged = jest
|
|
508
|
+
.spyOn(console, 'info')
|
|
509
|
+
.mockImplementation(() => undefined);
|
|
510
|
+
const absent = PROVIDERS[1].descriptor.id;
|
|
511
|
+
|
|
512
|
+
// Saved explicitly, as the settings editor writes it. Enabled means the
|
|
513
|
+
// extension supports the assistant, not that it is installed.
|
|
514
|
+
const live = await activate({
|
|
515
|
+
composite: { [`providers.${absent}.enabled`]: true },
|
|
516
|
+
answers: [() => Promise.resolve(roster(absent))]
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
expect(
|
|
520
|
+
live.docked
|
|
521
|
+
.filter(widget => !widget.isDisposed)
|
|
522
|
+
.map(widget => widget.descriptor.id)
|
|
523
|
+
).not.toContain(absent);
|
|
524
|
+
expect(toasted).not.toHaveBeenCalled();
|
|
525
|
+
// The reason stays in the browser console, once.
|
|
526
|
+
expect(
|
|
527
|
+
logged.mock.calls
|
|
528
|
+
.map(args => String(args[0]))
|
|
529
|
+
.filter(line => line.includes(`"${absent}"`))
|
|
530
|
+
).toHaveLength(1);
|
|
531
|
+
|
|
532
|
+
toasted.mockRestore();
|
|
533
|
+
logged.mockRestore();
|
|
534
|
+
live.docked.forEach(widget => widget.dispose());
|
|
535
|
+
});
|
|
536
|
+
|
|
503
537
|
it('ACC-PROV-16 - a binary installed after start gains its panel on the next probe', async () => {
|
|
504
538
|
const late = PROVIDERS[2].descriptor.id;
|
|
505
539
|
let present = false;
|
|
@@ -645,13 +645,15 @@ describe('DEF-46 - the row tooltip names only a mode the row launches with', ()
|
|
|
645
645
|
expect(tooltip([session()])).toContain('Launch mode: Skip Permissions');
|
|
646
646
|
});
|
|
647
647
|
|
|
648
|
-
it('
|
|
648
|
+
it('names the mode on a row a live worker holds, which is resumed too', () => {
|
|
649
649
|
panel.setModes({ skip: true });
|
|
650
|
-
//
|
|
651
|
-
//
|
|
650
|
+
// The click stops the worker and resumes the conversation, and a resume
|
|
651
|
+
// carries the mode (DEF-PROV-272).
|
|
652
652
|
const text = tooltip([session({ bg_id: 'abcd1234' })]);
|
|
653
|
-
expect(text).toContain(
|
|
654
|
-
|
|
653
|
+
expect(text).toContain(
|
|
654
|
+
'Background agent: abcd1234 (click stops it and resumes here)'
|
|
655
|
+
);
|
|
656
|
+
expect(text).toContain('Launch mode: Skip Permissions');
|
|
655
657
|
});
|
|
656
658
|
});
|
|
657
659
|
|
package/src/core/panel.ts
CHANGED
|
@@ -1899,9 +1899,9 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
1899
1899
|
);
|
|
1900
1900
|
name.appendChild(badge);
|
|
1901
1901
|
}
|
|
1902
|
-
// A live worker owns this conversation, so a click
|
|
1903
|
-
// resuming
|
|
1904
|
-
//
|
|
1902
|
+
// A live worker owns this conversation, so a click stops it before
|
|
1903
|
+
// resuming. Marked so the difference is visible before the click, not a
|
|
1904
|
+
// surprise after it. No own title: a child title shadows the
|
|
1905
1905
|
// row's, which would hide the path behind a two-character chip.
|
|
1906
1906
|
if (this._descriptor.hasBgAgents && session.bg_id) {
|
|
1907
1907
|
const bg = document.createElement('span');
|
|
@@ -2086,15 +2086,12 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2086
2086
|
lines.push('Status: active');
|
|
2087
2087
|
}
|
|
2088
2088
|
if (this._descriptor.hasBgAgents && s.bg_id) {
|
|
2089
|
-
lines.push(
|
|
2089
|
+
lines.push(
|
|
2090
|
+
`Background agent: ${s.bg_id} (click stops it and resumes here)`
|
|
2091
|
+
);
|
|
2090
2092
|
}
|
|
2091
2093
|
lines.push(...(this._hooks.tooltipLines?.(s) ?? []));
|
|
2092
|
-
|
|
2093
|
-
// the line above may have just said the click joins a live worker, and an
|
|
2094
|
-
// attach is issued before the mode is appended, so `_resolvedVariant`
|
|
2095
|
-
// promised a flag the server discards - one tooltip naming both, a line
|
|
2096
|
-
// apart (DEF-46).
|
|
2097
|
-
const variant = this._resumeVariant(s);
|
|
2094
|
+
const variant = this._resolvedVariant();
|
|
2098
2095
|
if (variant) {
|
|
2099
2096
|
// The row click launches with this, and the menu is the only other place
|
|
2100
2097
|
// that says so.
|
|
@@ -2230,30 +2227,6 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2230
2227
|
return resolved;
|
|
2231
2228
|
}
|
|
2232
2229
|
|
|
2233
|
-
/** The same mode as it applies to RESUME - of the active row by default, or
|
|
2234
|
-
* of whichever conversation is named.
|
|
2235
|
-
*
|
|
2236
|
-
* An attach is not a launch: the server returns the argv before the flag is
|
|
2237
|
-
* appended, deliberately, so promising the mode on a conversation a live
|
|
2238
|
-
* worker already holds would name something the assistant never receives.
|
|
2239
|
-
* That is true of resuming and of nothing else, so it is tested HERE rather
|
|
2240
|
-
* than in `_resolvedVariant` - which is read by the `+` button and both
|
|
2241
|
-
* new-session and branch items, none of which attach. Testing it there let
|
|
2242
|
-
* one right-click on a background-agent row strip the mode's name off every
|
|
2243
|
-
* one of those surfaces panel-wide, while their launches went on carrying it
|
|
2244
|
-
* (DEF-37).
|
|
2245
|
-
*
|
|
2246
|
-
* The argument is what lets a surface that describes SOMEONE ELSE'S resume -
|
|
2247
|
-
* a row tooltip built during a render, a branch item in the submenu - ask the
|
|
2248
|
-
* question about that conversation rather than about the active row.
|
|
2249
|
-
*/
|
|
2250
|
-
private _resumeVariant(
|
|
2251
|
-
target: { bg_id?: string | null } | null = this._activeSession
|
|
2252
|
-
): IResolvedLaunchMode | null {
|
|
2253
|
-
const joining = this._descriptor.hasBgAgents && !!target?.bg_id;
|
|
2254
|
-
return joining ? null : this._resolvedVariant();
|
|
2255
|
-
}
|
|
2256
|
-
|
|
2257
2230
|
/** The variant suffix a plain item wears while that mode is in force. */
|
|
2258
2231
|
private _variantSuffix(): string {
|
|
2259
2232
|
const resolved = this._resolvedVariant();
|
|
@@ -2302,15 +2275,14 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2302
2275
|
});
|
|
2303
2276
|
|
|
2304
2277
|
this._commands.addCommand(this._cmd('resume'), {
|
|
2305
|
-
//
|
|
2306
|
-
//
|
|
2307
|
-
// gets.
|
|
2278
|
+
// Resuming a conversation a live worker holds stops that worker first,
|
|
2279
|
+
// so a provider that has workers says which of the two this row gets.
|
|
2308
2280
|
label: () => {
|
|
2309
|
-
const variant = this.
|
|
2281
|
+
const variant = this._resolvedVariant();
|
|
2310
2282
|
const verb = this._hooks.resumeLabel?.(active()) ?? 'Resume';
|
|
2311
2283
|
return variant ? `${verb} (${variant.label})` : verb;
|
|
2312
2284
|
},
|
|
2313
|
-
icon: () => (this.
|
|
2285
|
+
icon: () => (this._resolvedVariant()?.unsafe ? shieldIcon : undefined),
|
|
2314
2286
|
execute: () => {
|
|
2315
2287
|
const s = active();
|
|
2316
2288
|
if (s) {
|
|
@@ -2321,15 +2293,14 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2321
2293
|
|
|
2322
2294
|
for (const mode of this._variantModes) {
|
|
2323
2295
|
this._commands.addCommand(this._cmd(`resume-${mode.id}`), {
|
|
2324
|
-
|
|
2296
|
+
// The same verb as the plain item, so a row a live worker holds says
|
|
2297
|
+
// here too that the worker is stopped first.
|
|
2298
|
+
label: () =>
|
|
2299
|
+
`${this._hooks.resumeLabel?.(active()) ?? 'Resume'} (${mode.menuLabel})`,
|
|
2325
2300
|
icon: shieldIcon,
|
|
2326
2301
|
// Absent while the plain item already resolves to this same mode - it
|
|
2327
2302
|
// would be a second entry building an identical launch.
|
|
2328
2303
|
isVisible: () => !this._buildsSameLaunch(mode),
|
|
2329
|
-
// A conversation a live worker already holds was started in whatever
|
|
2330
|
-
// mode that worker has; joining it cannot change that, so the variant
|
|
2331
|
-
// is disabled rather than silently joining in the wrong mode.
|
|
2332
|
-
isEnabled: () => !(this._descriptor.hasBgAgents && active()?.bg_id),
|
|
2333
2304
|
execute: () => {
|
|
2334
2305
|
const s = active();
|
|
2335
2306
|
if (s) {
|
|
@@ -2502,14 +2473,10 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2502
2473
|
this._commands.addCommand(this._cmd('open-branch'), {
|
|
2503
2474
|
// Names the mode the launch carries, like Resume, `+` and Branch Session
|
|
2504
2475
|
// do - this item opens a terminal, so the mode in force applies to it and
|
|
2505
|
-
// this was the one launch surface that stayed silent about it.
|
|
2506
|
-
// aware per conversation: the submenu passes `bg`, since a branch a live
|
|
2507
|
-
// worker holds is attached to and takes no mode (DEF-46's rule).
|
|
2476
|
+
// this was the one launch surface that stayed silent about it.
|
|
2508
2477
|
label: args => {
|
|
2509
2478
|
const base = String(args.label ?? '');
|
|
2510
|
-
const variant = this.
|
|
2511
|
-
args.bg ? { bg_id: String(args.bg) } : null
|
|
2512
|
-
);
|
|
2479
|
+
const variant = this._resolvedVariant();
|
|
2513
2480
|
return variant ? `${base} (${variant.label})` : base;
|
|
2514
2481
|
},
|
|
2515
2482
|
// Intent glyph, not row state - see switch-branch above.
|
|
@@ -2844,10 +2811,7 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2844
2811
|
command: this._cmd('open-branch'),
|
|
2845
2812
|
args: {
|
|
2846
2813
|
session_id: b.session_id,
|
|
2847
|
-
label: this._branchMenuLabel(b)
|
|
2848
|
-
// The label reads this to tell an attach from a launch; the
|
|
2849
|
-
// provider's own worker capability is tested where it is answered.
|
|
2850
|
-
...(b.bg_id ? { bg: b.bg_id } : {})
|
|
2814
|
+
label: this._branchMenuLabel(b)
|
|
2851
2815
|
}
|
|
2852
2816
|
});
|
|
2853
2817
|
}
|
|
@@ -2875,7 +2839,7 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2875
2839
|
branchName: b => this._branchDisplayName(b),
|
|
2876
2840
|
formatTime: ms => this._formatRelativeTime(ms),
|
|
2877
2841
|
branchBadge: b => this._bgBadgeFor(b),
|
|
2878
|
-
openTitle:
|
|
2842
|
+
openTitle: () => this._openBranchTitle(session),
|
|
2879
2843
|
onSwitch: id => void this._switchBranch(session, id),
|
|
2880
2844
|
onOpen: id => void this._openSession(session, undefined, id),
|
|
2881
2845
|
onDelete: ids => this._deleteBranches(session, ids),
|
|
@@ -2888,10 +2852,8 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2888
2852
|
|
|
2889
2853
|
/** Tooltip for one Open button in the Manage Sessions popup. Same rule as the
|
|
2890
2854
|
* Open Branched Conversation submenu: the button launches a terminal, so it
|
|
2891
|
-
* names the mode that launch carries
|
|
2892
|
-
|
|
2893
|
-
* `_lastBranches`, so it answers from the session. */
|
|
2894
|
-
private _openBranchTitle(session: ISession, sessionId: string): string {
|
|
2855
|
+
* names the mode that launch carries. */
|
|
2856
|
+
private _openBranchTitle(session: ISession): string {
|
|
2895
2857
|
// One terminal serves a project-scoped assistant, so the button focuses
|
|
2896
2858
|
// or starts it, under the provider's own verb for that.
|
|
2897
2859
|
if (this._descriptor.terminalScope === 'project') {
|
|
@@ -2900,11 +2862,7 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2900
2862
|
);
|
|
2901
2863
|
}
|
|
2902
2864
|
const base = 'Open this conversation in its own terminal';
|
|
2903
|
-
const
|
|
2904
|
-
sessionId === session.session_id
|
|
2905
|
-
? session
|
|
2906
|
-
: (this._lastBranches.find(b => b.session_id === sessionId) ?? null);
|
|
2907
|
-
const variant = this._resumeVariant(target);
|
|
2865
|
+
const variant = this._resolvedVariant();
|
|
2908
2866
|
return variant ? `${base} (${variant.label})` : base;
|
|
2909
2867
|
}
|
|
2910
2868
|
|
package/src/core/terminals.ts
CHANGED
|
@@ -65,8 +65,8 @@ function warnLegacyCompanion(): void {
|
|
|
65
65
|
// And said where the user is looking. The symptom is tabs that are simply
|
|
66
66
|
// plain, which reads as a feature that was never on rather than as something
|
|
67
67
|
// to fix, and a console line only reaches someone already suspecting the
|
|
68
|
-
// cause. The
|
|
69
|
-
//
|
|
68
|
+
// cause. The retired-extension notice in index.ts pairs its console line
|
|
69
|
+
// with a toast the same way. It does not
|
|
70
70
|
// close on its own, because the condition does not go away on its own.
|
|
71
71
|
Notification.warning(message, { autoClose: false });
|
|
72
72
|
}
|
package/src/core/types.ts
CHANGED
|
@@ -349,7 +349,8 @@ export interface ITabColourEvents {
|
|
|
349
349
|
* shared panel. */
|
|
350
350
|
export interface IProviderHooks {
|
|
351
351
|
/** Label for the primary open action, when the provider distinguishes verbs
|
|
352
|
-
* (
|
|
352
|
+
* (stopping a live worker before resuming is not the same as resuming a
|
|
353
|
+
* dormant store entry). */
|
|
353
354
|
resumeLabel?: (session: ISession | null) => string;
|
|
354
355
|
/** Extra lines for the row tooltip. */
|
|
355
356
|
tooltipLines?: (session: ISession) => string[];
|
package/src/index.ts
CHANGED
|
@@ -333,46 +333,30 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
333
333
|
const roster = status;
|
|
334
334
|
const available =
|
|
335
335
|
roster === null ? true : probe !== null && probe.available !== false;
|
|
336
|
-
// Enabled but binary absent
|
|
337
|
-
//
|
|
338
|
-
//
|
|
339
|
-
// Only when the probe SUCCEEDED and this provider is
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
// assistant installed would otherwise raise three toasts about assistants
|
|
343
|
-
// the user has never heard of. The console line stays for that case.
|
|
344
|
-
const chosen =
|
|
345
|
-
settings?.get(`providers.${id}.enabled`).user !== undefined;
|
|
336
|
+
// Enabled but binary absent is logged to the console, never announced:
|
|
337
|
+
// enabled means the extension supports the assistant, not that it is
|
|
338
|
+
// installed, so a toast would name a tool the user never installed
|
|
339
|
+
// (DEF-PANE-277). Only when the probe SUCCEEDED and this provider is
|
|
340
|
+
// the one missing - blaming PATH for a server the frontend never
|
|
341
|
+
// reached names the wrong cause. Once per id, not per reconcile.
|
|
346
342
|
if (
|
|
347
343
|
probe !== null &&
|
|
348
344
|
probe.available === false &&
|
|
349
345
|
enabled &&
|
|
350
|
-
chosen &&
|
|
351
346
|
!warnedUnavailable.has(id)
|
|
352
347
|
) {
|
|
353
348
|
warnedUnavailable.add(id);
|
|
354
349
|
const binary = registry.get(id)?.descriptor.cliBinary ?? id;
|
|
355
|
-
const label = registry.get(id)?.descriptor.label ?? id;
|
|
356
350
|
console.info(
|
|
357
351
|
`${LOG_PREFIX} "${id}" is enabled but its \`${binary}\` binary was not found on the server's PATH; panel not shown.`
|
|
358
352
|
);
|
|
359
|
-
// And say it where the user is looking. A console line satisfies the
|
|
360
|
-
// comment above only for someone who already suspects the cause; the
|
|
361
|
-
// symptom is a panel that never appears, which reads as a broken
|
|
362
|
-
// install rather than a PATH difference between the Jupyter server
|
|
363
|
-
// and the user's shell. The retired-extension case next door already
|
|
364
|
-
// gets a notification, so the COMMON failure was the quiet one.
|
|
365
|
-
Notification.warning(
|
|
366
|
-
`${label} is enabled but \`${binary}\` was not found on the Jupyter server's PATH, so its panel is not shown.`,
|
|
367
|
-
{ autoClose: 8000 }
|
|
368
|
-
);
|
|
369
353
|
}
|
|
370
354
|
// The latch is per ABSENCE, not per page load. Once the server reports
|
|
371
|
-
// the binary present again, a later disappearance gets its own
|
|
355
|
+
// the binary present again, a later disappearance gets its own line:
|
|
372
356
|
// without this, one transient absence - a CLI upgrade unlinking the
|
|
373
357
|
// binary for a moment, or an obsolete roster from an overlapping probe -
|
|
374
358
|
// permanently suppresses the real one (DEF-125). It cannot make the
|
|
375
|
-
//
|
|
359
|
+
// line repeat while the state is unchanged, which is what the
|
|
376
360
|
// once-per-id latch above exists to prevent: the two conditions are
|
|
377
361
|
// mutually exclusive.
|
|
378
362
|
if (probe !== null && probe.available !== false) {
|
package/src/providers/claude.ts
CHANGED
|
@@ -76,8 +76,8 @@ export const descriptor: IProviderDescriptor = {
|
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
export const hooks: IProviderHooks = {
|
|
79
|
-
//
|
|
80
|
-
//
|
|
79
|
+
// Opening a conversation a live agent holds stops the agent first, so the
|
|
80
|
+
// item says so before the click (DEF-PROV-272).
|
|
81
81
|
resumeLabel: (session: ISession | null) =>
|
|
82
|
-
session?.bg_id ? '
|
|
82
|
+
session?.bg_id ? 'Stop Background Agent and Resume' : 'Resume'
|
|
83
83
|
};
|