jupyterlab_ai_code_assistants_extension 1.0.19 → 1.0.23
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/icons.d.ts +1 -0
- package/lib/core/icons.js +16 -7
- package/lib/core/modes.d.ts +3 -6
- package/lib/core/modes.js +11 -31
- package/lib/core/panel.d.ts +1 -3
- package/lib/core/panel.js +14 -31
- package/lib/core/types.d.ts +6 -19
- package/lib/providers/claude.js +0 -1
- package/lib/providers/codex.js +0 -1
- package/lib/providers/gemini.js +2 -14
- package/lib/providers/kimi.js +0 -1
- package/package.json +1 -1
- package/schema/plugin.json +0 -7
- package/src/__tests__/launch-mode.spec.ts +18 -54
- package/src/__tests__/panel.spec.ts +18 -17
- package/src/__tests__/registry.spec.ts +2 -7
- package/src/core/icons.ts +18 -7
- package/src/core/modes.ts +11 -33
- package/src/core/panel.ts +13 -32
- package/src/core/types.ts +6 -19
- package/src/providers/claude.ts +0 -1
- package/src/providers/codex.ts +0 -1
- package/src/providers/gemini.ts +2 -15
- package/src/providers/kimi.ts +0 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Chat-panel extensions re-implement the agent loop and trail the real tool. This
|
|
|
32
32
|
- **One-click resume** - click a row to jump back into that session in a terminal; an open terminal for the project is reused instead of duplicated
|
|
33
33
|
- **Conversation switcher** - a right-click "Switch and Manage Sessions" submenu lists a project's other conversations by name and short id with last-activity time; "Manage Sessions..." opens a searchable popup over the full list with multi-select delete and per-row open and copy-id buttons
|
|
34
34
|
- **Branch session** - fork the current conversation into a new named session via the right-click menu; each assistant forks its own way (Claude's native `--fork-session`, Codex's `codex fork`, server-side copies for Kimi and Gemini) behind the same menu item
|
|
35
|
-
- **Launch modes under each assistant's own name** - Claude's skip-permissions, Codex's approval bypass, Kimi's `--yolo`, Gemini's YOLO
|
|
35
|
+
- **Launch modes under each assistant's own name** - Claude's skip-permissions, Codex's approval bypass, Kimi's `--yolo`, Gemini's YOLO; unsafe variants carry a shield glyph in the launch menus
|
|
36
36
|
- **Coloured terminal tabs** - each session's colour tints its terminal tab via the companion `jupyterlab_colourful_tab_extension` (installed automatically). Claude's own `/color` supplies its default, Kimi derives a stable colour from the conversation id; Codex and Gemini have no colour of their own until you set one, and a branched session inherits its parent's colour, which it keeps even if the parent's colour later changes or is reset
|
|
37
37
|
- **Your own tab colour wins** - set a colour on a terminal tab and the extension remembers it for that conversation, overriding whatever the assistant chose. `Reset Tab Colour (n)` in the session's right-click menu hands back every colour you set by hand on that project's conversations, and appears once there is one to hand back. Use it rather than the tab menu's own Clear, which will not release a stored colour. A Codex or Kimi conversation that has never been resumed cannot be tracked yet, so a colour set on its tab is not kept
|
|
38
38
|
- **Favorites** - star projects you keep coming back to via the right-click menu; favourites from the standalone extensions are migrated on first run
|
package/lib/core/icons.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export declare const assistantsIcon: LabIcon;
|
|
|
3
3
|
export declare const starFilledIcon: LabIcon;
|
|
4
4
|
export declare const refreshIcon: LabIcon;
|
|
5
5
|
export declare const removeIcon: LabIcon;
|
|
6
|
+
export declare const cleanupIcon: LabIcon;
|
|
6
7
|
export declare const shieldIcon: LabIcon;
|
|
7
8
|
export declare const addIcon: LabIcon;
|
|
8
9
|
export declare const branchIcon: LabIcon;
|
package/lib/core/icons.js
CHANGED
|
@@ -37,19 +37,28 @@ export const removeIcon = new LabIcon({
|
|
|
37
37
|
name: `${ICON_PREFIX}:remove`,
|
|
38
38
|
svgstr: removeSvgStr
|
|
39
39
|
});
|
|
40
|
+
// `Clean Up Parallel Sessions` - deletes several at once, so it wears the
|
|
41
|
+
// sweep variant of the trash glyph (Material "delete_sweep"), same family as
|
|
42
|
+
// `Remove from ...`'s plain trash but visibly the bulk one (user direction
|
|
43
|
+
// 2026-08-11, replacing the shared danger marker it wore under DEF-45).
|
|
44
|
+
const cleanupSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
|
|
45
|
+
<path class="jp-icon3" fill="#616161" d="M15 16h4v2h-4zm0-8h7v2h-7zm0 4h6v2h-6zM3 18c0 1.1.9 2 2 2h6c1.1 0 2-.9 2-2V8H3v10zM14 5h-3l-1-1H6L5 5H2v2h12z"/>
|
|
46
|
+
</svg>`;
|
|
47
|
+
export const cleanupIcon = new LabIcon({
|
|
48
|
+
name: `${ICON_PREFIX}:cleanup`,
|
|
49
|
+
svgstr: cleanupSvgStr
|
|
50
|
+
});
|
|
40
51
|
// Marks the unsafe launch variants. The shield is the established marker for
|
|
41
52
|
// an action that touches the permission system (the UAC pattern), and it is
|
|
42
53
|
// what the reference implementation used; Material "shield" half-fill, kept
|
|
43
54
|
// per user direction 2026-08-11 over the earlier caution triangle.
|
|
44
55
|
//
|
|
45
|
-
// 16x16
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
// the
|
|
49
|
-
// whole job is to say "this differs" was the least conspicuous in the menu
|
|
50
|
-
// (DEF-40, and the reason the reference's 13x13 grey is not copied verbatim).
|
|
56
|
+
// 16x16 like its siblings (DEF-40's size half - authored at 13x13 it
|
|
57
|
+
// rendered smaller than every icon beside it), and in the standard `jp-icon3`
|
|
58
|
+
// like them too: the user ruled 2026-08-11 that the shield shape alone marks
|
|
59
|
+
// the difference, reversing DEF-40's colour half (the orange `jp-icon-warn0`).
|
|
51
60
|
const shieldSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
|
|
52
|
-
<path class="jp-
|
|
61
|
+
<path class="jp-icon3" fill="#616161" d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/>
|
|
53
62
|
</svg>`;
|
|
54
63
|
export const shieldIcon = new LabIcon({
|
|
55
64
|
name: `${ICON_PREFIX}:shield`,
|
package/lib/core/modes.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { ILaunchMode } from './types';
|
|
|
2
2
|
/** A resolved launch mode, with what to call it and whether to warn. */
|
|
3
3
|
export interface IResolvedLaunchMode {
|
|
4
4
|
mode: ILaunchMode;
|
|
5
|
-
/** What the menu should call it - the
|
|
5
|
+
/** What the menu should call it - the mode's own label. */
|
|
6
6
|
label: string;
|
|
7
7
|
/** Whether this widens what the assistant may do without asking. */
|
|
8
8
|
unsafe: boolean;
|
|
@@ -12,11 +12,8 @@ export interface IResolvedLaunchMode {
|
|
|
12
12
|
*
|
|
13
13
|
* The payload carries at most one mode, because the server maps one token to
|
|
14
14
|
* one flag. `force` wins outright - that is what the menu's variant entries
|
|
15
|
-
* do. Otherwise
|
|
16
|
-
*
|
|
17
|
-
* left on must not silently invert it. A boolean fires only while every enum
|
|
18
|
-
* mode sits at its default. An enum at its default sends nothing - it is what
|
|
19
|
-
* the CLI would do unasked.
|
|
15
|
+
* do. Otherwise the first switch that is on fires. Every mode is a boolean
|
|
16
|
+
* skip-approval switch, so "on" always means the same thing.
|
|
20
17
|
*/
|
|
21
18
|
export declare function resolveLaunchMode(launchModes: ILaunchMode[], values: Record<string, boolean | string>, force?: string): string | undefined;
|
|
22
19
|
/**
|
package/lib/core/modes.js
CHANGED
|
@@ -1,34 +1,22 @@
|
|
|
1
1
|
// Launch-mode resolution: which single mode token, if any, one launch carries.
|
|
2
2
|
//
|
|
3
3
|
// Its own module because the panel is the only caller but not the only reader -
|
|
4
|
-
// the
|
|
5
|
-
//
|
|
4
|
+
// the rule that one launch carries at most one mode is worth asserting on its
|
|
5
|
+
// own, and this file pulls in no JupyterLab surface.
|
|
6
6
|
/**
|
|
7
7
|
* The single launch-mode token for one action, or undefined for a plain launch.
|
|
8
8
|
*
|
|
9
9
|
* The payload carries at most one mode, because the server maps one token to
|
|
10
10
|
* one flag. `force` wins outright - that is what the menu's variant entries
|
|
11
|
-
* do. Otherwise
|
|
12
|
-
*
|
|
13
|
-
* left on must not silently invert it. A boolean fires only while every enum
|
|
14
|
-
* mode sits at its default. An enum at its default sends nothing - it is what
|
|
15
|
-
* the CLI would do unasked.
|
|
11
|
+
* do. Otherwise the first switch that is on fires. Every mode is a boolean
|
|
12
|
+
* skip-approval switch, so "on" always means the same thing.
|
|
16
13
|
*/
|
|
17
14
|
export function resolveLaunchMode(launchModes, values, force) {
|
|
18
15
|
if (force) {
|
|
19
16
|
return force;
|
|
20
17
|
}
|
|
21
18
|
for (const mode of launchModes) {
|
|
22
|
-
|
|
23
|
-
if (mode.kind !== 'boolean' &&
|
|
24
|
-
typeof value === 'string' &&
|
|
25
|
-
value &&
|
|
26
|
-
value !== mode.default) {
|
|
27
|
-
return `${mode.id}=${value}`;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
for (const mode of launchModes) {
|
|
31
|
-
if (mode.kind === 'boolean' && values[mode.id] === true) {
|
|
19
|
+
if (values[mode.id] === true) {
|
|
32
20
|
return mode.id;
|
|
33
21
|
}
|
|
34
22
|
}
|
|
@@ -45,28 +33,20 @@ export function resolveLaunchMode(launchModes, values, force) {
|
|
|
45
33
|
* assistant's own wording.
|
|
46
34
|
*/
|
|
47
35
|
export function resolvedLaunchModeEntry(launchModes, values) {
|
|
48
|
-
var _a
|
|
36
|
+
var _a;
|
|
49
37
|
const token = resolveLaunchMode(launchModes, values);
|
|
50
38
|
if (!token) {
|
|
51
39
|
return null;
|
|
52
40
|
}
|
|
53
|
-
const
|
|
54
|
-
const mode = launchModes.find(m => m.id === id);
|
|
41
|
+
const mode = launchModes.find(m => m.id === token);
|
|
55
42
|
if (!mode) {
|
|
56
43
|
return null;
|
|
57
44
|
}
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// to a mode and is stricter than the default, so "resolved at all" is the
|
|
61
|
-
// wrong question to ask about danger.
|
|
62
|
-
const unsafe = value
|
|
63
|
-
? ((_a = mode.unsafeValues) !== null && _a !== void 0 ? _a : []).indexOf(value) !== -1
|
|
64
|
-
: !!mode.menuLabel;
|
|
65
|
-
// Name the VALUE where there is one - three enum values sharing the mode's
|
|
66
|
-
// title render as one indistinguishable label.
|
|
45
|
+
// Carrying a menu label IS a boolean mode's declaration that it skips
|
|
46
|
+
// approval - it is what puts a variant in the menu.
|
|
67
47
|
return {
|
|
68
48
|
mode,
|
|
69
|
-
label: (
|
|
70
|
-
unsafe
|
|
49
|
+
label: (_a = mode.menuLabel) !== null && _a !== void 0 ? _a : mode.title,
|
|
50
|
+
unsafe: !!mode.menuLabel
|
|
71
51
|
};
|
|
72
52
|
}
|
package/lib/core/panel.d.ts
CHANGED
|
@@ -188,7 +188,7 @@ export declare class AssistantSessionsPanel extends Widget {
|
|
|
188
188
|
private _setRefreshSpinning;
|
|
189
189
|
private _setActiveRow;
|
|
190
190
|
private _cmd;
|
|
191
|
-
/**
|
|
191
|
+
/** Launch modes that produce a menu variant - the assistant's own
|
|
192
192
|
* skip-approval switch, under the assistant's own name. */
|
|
193
193
|
private get _variantModes();
|
|
194
194
|
/** The launch mode a PLAIN action resolves to right now, or null.
|
|
@@ -298,8 +298,6 @@ export declare class AssistantSessionsPanel extends Widget {
|
|
|
298
298
|
private _newSessionMenu;
|
|
299
299
|
/** Re-writes the `+` button's title. Set once the shell exists. */
|
|
300
300
|
private _renameNewButton;
|
|
301
|
-
/** Re-draws the `+` button's glyph. Set once the shell exists. */
|
|
302
|
-
private _repaintNewIcon;
|
|
303
301
|
private _lastBranches;
|
|
304
302
|
private _lastBranchesCurrent;
|
|
305
303
|
private _colouredTabs;
|
package/lib/core/panel.js
CHANGED
|
@@ -4,7 +4,7 @@ import { CommandRegistry } from '@lumino/commands';
|
|
|
4
4
|
import { UUID } from '@lumino/coreutils';
|
|
5
5
|
import { Widget } from '@lumino/widgets';
|
|
6
6
|
import { ColourStore } from './colour';
|
|
7
|
-
import { addIcon, branchIcon, filterIcon, providerIcon, refreshIcon, removeIcon, shieldIcon, starFilledIcon, switchIcon } from './icons';
|
|
7
|
+
import { addIcon, branchIcon, cleanupIcon, filterIcon, providerIcon, refreshIcon, removeIcon, shieldIcon, starFilledIcon, switchIcon } from './icons';
|
|
8
8
|
import { DEFAULT_RECENT_LIMIT, MAX_RECENT_LIMIT, MIN_RECENT_LIMIT } from './limits';
|
|
9
9
|
import { resolveLaunchMode, resolvedLaunchModeEntry } from './modes';
|
|
10
10
|
import { showManageSessionsPopup } from './popup';
|
|
@@ -107,8 +107,6 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
107
107
|
this._roving = {};
|
|
108
108
|
/** Re-writes the `+` button's title. Set once the shell exists. */
|
|
109
109
|
this._renameNewButton = null;
|
|
110
|
-
/** Re-draws the `+` button's glyph. Set once the shell exists. */
|
|
111
|
-
this._repaintNewIcon = null;
|
|
112
110
|
this._lastBranches = [];
|
|
113
111
|
this._lastBranchesCurrent = '';
|
|
114
112
|
this._colouredTabs = true;
|
|
@@ -220,14 +218,12 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
220
218
|
/** Current values of this provider's launch modes, read from settings. Each
|
|
221
219
|
* key is an `ILaunchMode.id` in the assistant's own terminology. */
|
|
222
220
|
setModes(modes) {
|
|
223
|
-
var _a
|
|
221
|
+
var _a;
|
|
224
222
|
this._modes = { ...modes };
|
|
225
223
|
// Menu labels are lazy and re-read this on every open; the button's title
|
|
226
|
-
// is a written string, so it has to be rewritten here.
|
|
224
|
+
// is a written string, so it has to be rewritten here. The glyph needs no
|
|
225
|
+
// such hook - it is always "+", whatever mode is armed (DEF-112).
|
|
227
226
|
(_a = this._renameNewButton) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
228
|
-
// Same for its glyph - a menu item re-reads its icon on every open, a
|
|
229
|
-
// painted button does not.
|
|
230
|
-
(_b = this._repaintNewIcon) === null || _b === void 0 ? void 0 : _b.call(this);
|
|
231
227
|
}
|
|
232
228
|
setColouredTabs(on) {
|
|
233
229
|
this._colouredTabs = on;
|
|
@@ -282,25 +278,11 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
282
278
|
// after turning an approval-free mode on (DEF-38).
|
|
283
279
|
this._renameNewButton = nameNewButton;
|
|
284
280
|
nameNewButton();
|
|
285
|
-
// The glyph
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
// wearing no warning at all.
|
|
291
|
-
const paintNewIcon = () => {
|
|
292
|
-
var _a;
|
|
293
|
-
const icon = (_a = (this._visibleVariantCount() === 0 ? this._variantIcon() : undefined)) !== null && _a !== void 0 ? _a : addIcon;
|
|
294
|
-
// `LabIcon.element` appends into the container, so the previous glyph
|
|
295
|
-
// has to go or the button would collect one svg per settings change.
|
|
296
|
-
newBtn.replaceChildren();
|
|
297
|
-
icon.element({ container: newBtn });
|
|
298
|
-
};
|
|
299
|
-
// Held for the same reason the title is: the shell is built before
|
|
300
|
-
// settings arrive, so what is drawn here can only say "no mode in force"
|
|
301
|
-
// and `setModes` has to repaint it (DEF-38).
|
|
302
|
-
this._repaintNewIcon = paintNewIcon;
|
|
303
|
-
paintNewIcon();
|
|
281
|
+
// The glyph is always "+", whatever mode is armed (DEF-112): the shield
|
|
282
|
+
// marks the menu ENTRIES that skip approval, while the button that offers
|
|
283
|
+
// or launches stays neutral - an armed mode is still named in the title
|
|
284
|
+
// above, so the launch never goes unnamed.
|
|
285
|
+
addIcon.element({ container: newBtn });
|
|
304
286
|
newBtn.addEventListener('click', () => {
|
|
305
287
|
// With a mode in force the variant items suppress themselves, leaving a
|
|
306
288
|
// dropdown with one entry - two clicks for its only outcome. Do it.
|
|
@@ -1637,10 +1619,10 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
1637
1619
|
_cmd(action) {
|
|
1638
1620
|
return commandId(this._descriptor.id, action);
|
|
1639
1621
|
}
|
|
1640
|
-
/**
|
|
1622
|
+
/** Launch modes that produce a menu variant - the assistant's own
|
|
1641
1623
|
* skip-approval switch, under the assistant's own name. */
|
|
1642
1624
|
get _variantModes() {
|
|
1643
|
-
return this._descriptor.launchModes.filter(m =>
|
|
1625
|
+
return this._descriptor.launchModes.filter(m => !!m.menuLabel);
|
|
1644
1626
|
}
|
|
1645
1627
|
/** The launch mode a PLAIN action resolves to right now, or null.
|
|
1646
1628
|
*
|
|
@@ -1839,8 +1821,9 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
1839
1821
|
// Marked, like `Remove from ...` below it. The two share the group the
|
|
1840
1822
|
// separator was added to protect, and both delete history that cannot be
|
|
1841
1823
|
// recovered; leaving this one bare made the pair read as one dangerous
|
|
1842
|
-
// item beside an ordinary one (DEF-45).
|
|
1843
|
-
|
|
1824
|
+
// item beside an ordinary one (DEF-45). The sweep variant of the trash
|
|
1825
|
+
// glyph, because this one deletes several at once.
|
|
1826
|
+
icon: cleanupIcon,
|
|
1844
1827
|
isVisible: () => { var _a, _b; return ((_b = (_a = active()) === null || _a === void 0 ? void 0 : _a.extra_sessions) !== null && _b !== void 0 ? _b : 0) > 0; },
|
|
1845
1828
|
execute: () => {
|
|
1846
1829
|
const s = active();
|
package/lib/core/types.d.ts
CHANGED
|
@@ -19,9 +19,8 @@ export type ForkStrategy = 'native-flag' | 'native-command' | 'server-copy';
|
|
|
19
19
|
* the extension's own write-back store always wins over all three. */
|
|
20
20
|
export type ColourSource = 'native' | 'derived' | 'none';
|
|
21
21
|
/** One launch-behaviour setting of an assistant, in that assistant's own
|
|
22
|
-
* terminology.
|
|
23
|
-
*
|
|
24
|
-
* `enum` modes are settings-only choices passed through on every launch. */
|
|
22
|
+
* terminology. A launch mode is a boolean skip-approval switch: it gets a
|
|
23
|
+
* settings toggle (off by default) AND a context-menu launch variant. */
|
|
25
24
|
export interface ILaunchMode {
|
|
26
25
|
/** Settings key under `providers.<id>.` and launch-payload key. Uses the
|
|
27
26
|
* assistant's own terminology, never a normalised core name. */
|
|
@@ -30,24 +29,12 @@ export interface ILaunchMode {
|
|
|
30
29
|
title: string;
|
|
31
30
|
/** Settings description. */
|
|
32
31
|
description: string;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
values?: string[];
|
|
36
|
-
/** Default value. Boolean modes default to `false` without exception. */
|
|
37
|
-
default: boolean | string;
|
|
32
|
+
/** Default value - `false` without exception. */
|
|
33
|
+
default: boolean;
|
|
38
34
|
/** Menu label for the launch variant this mode produces, e.g.
|
|
39
|
-
* `Skip Permissions`.
|
|
35
|
+
* `Skip Permissions`. Carrying one IS the mode's declaration that it skips
|
|
36
|
+
* approval - it is what puts a variant in the menu. */
|
|
40
37
|
menuLabel?: string;
|
|
41
|
-
/** `enum` modes only: the values that WIDEN what the assistant may do
|
|
42
|
-
* without asking. The panel warns on these and names the value; every other
|
|
43
|
-
* value is a settings choice and is marked as one.
|
|
44
|
-
*
|
|
45
|
-
* Declared per provider rather than inferred, because "a mode resolved at
|
|
46
|
-
* all" is not the same question: Gemini's `plan` is read-only - stricter
|
|
47
|
-
* than the default - and marking it the same as `yolo` says nothing about
|
|
48
|
-
* either. A boolean mode needs no entry here; carrying a `menuLabel` is
|
|
49
|
-
* already its declaration that it skips approval. */
|
|
50
|
-
unsafeValues?: string[];
|
|
51
38
|
}
|
|
52
39
|
/** Everything the core needs to know about one assistant. Pure data - no
|
|
53
40
|
* JupyterLab imports - so the settings-schema generator can read it under
|
package/lib/providers/claude.js
CHANGED
|
@@ -52,7 +52,6 @@ export const descriptor = {
|
|
|
52
52
|
id: 'dangerouslySkipPermissions',
|
|
53
53
|
title: 'Dangerously skip permissions',
|
|
54
54
|
description: 'When enabled, newly spawned and resumed sessions are launched with `--dangerously-skip-permissions`. Bypasses every Claude Code permission prompt - only enable in sandboxes you trust.',
|
|
55
|
-
kind: 'boolean',
|
|
56
55
|
default: false,
|
|
57
56
|
menuLabel: 'Skip Permissions'
|
|
58
57
|
}
|
package/lib/providers/codex.js
CHANGED
|
@@ -48,7 +48,6 @@ export const descriptor = {
|
|
|
48
48
|
id: BYPASS_MODE,
|
|
49
49
|
title: 'Bypass Codex approvals and sandbox',
|
|
50
50
|
description: 'When enabled, newly spawned and resumed sessions are launched with `--dangerously-bypass-approvals-and-sandbox`. Bypasses every Codex approval prompt and sandbox - only enable in environments you trust.',
|
|
51
|
-
kind: 'boolean',
|
|
52
51
|
default: false,
|
|
53
52
|
menuLabel: 'Bypass Approvals'
|
|
54
53
|
}
|
package/lib/providers/gemini.js
CHANGED
|
@@ -28,11 +28,10 @@
|
|
|
28
28
|
const geminiSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 65 65" width="16" height="16">
|
|
29
29
|
<path class="jp-icon3" fill="#616161" d="M32.447 0c.68 0 1.273.465 1.439 1.125a38.904 38.904 0 001.999 5.905c2.152 5 5.105 9.376 8.854 13.125 3.751 3.75 8.126 6.703 13.125 8.855a38.98 38.98 0 005.906 1.999c.66.166 1.124.758 1.124 1.438 0 .68-.464 1.273-1.125 1.439a38.902 38.902 0 00-5.905 1.999c-5 2.152-9.375 5.105-13.125 8.854-3.749 3.751-6.702 8.126-8.854 13.125a38.973 38.973 0 00-2 5.906 1.485 1.485 0 01-1.438 1.124c-.68 0-1.272-.464-1.438-1.125a38.913 38.913 0 00-2-5.905c-2.151-5-5.103-9.375-8.854-13.125-3.75-3.749-8.125-6.702-13.125-8.854a38.973 38.973 0 00-5.905-2A1.485 1.485 0 010 32.448c0-.68.465-1.272 1.125-1.438a38.903 38.903 0 005.905-2c5-2.151 9.376-5.104 13.125-8.854 3.75-3.749 6.703-8.125 8.855-13.125a38.972 38.972 0 001.999-5.905A1.485 1.485 0 0132.447 0z"/>
|
|
30
30
|
</svg>`;
|
|
31
|
-
// Gemini's own
|
|
32
|
-
// terminology rather than normalised, because
|
|
31
|
+
// Gemini's own name for its approval switch. Kept in the assistant's
|
|
32
|
+
// terminology rather than normalised, because it is both the settings key
|
|
33
33
|
// under `providers.gemini.` and the mode token the server maps to a CLI flag.
|
|
34
34
|
const YOLO_MODE = 'yoloMode';
|
|
35
|
-
const APPROVAL_MODE = 'approvalMode';
|
|
36
35
|
export const descriptor = {
|
|
37
36
|
id: 'gemini',
|
|
38
37
|
label: 'Gemini',
|
|
@@ -54,19 +53,8 @@ export const descriptor = {
|
|
|
54
53
|
id: YOLO_MODE,
|
|
55
54
|
title: 'YOLO mode',
|
|
56
55
|
description: 'When enabled, newly spawned and resumed sessions are launched with `--yolo`. Gemini auto-approves every tool call without asking - only enable in workspaces you trust.',
|
|
57
|
-
kind: 'boolean',
|
|
58
56
|
default: false,
|
|
59
57
|
menuLabel: 'YOLO'
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
id: APPROVAL_MODE,
|
|
63
|
-
title: 'Approval mode',
|
|
64
|
-
description: 'Approval ladder passed as `--approval-mode` when set to a value other than the default. A non-default value here takes precedence over the YOLO switch; only the forced YOLO menu action overrides it. `default` prompts for approval, `auto_edit` auto-approves edit tools, `yolo` auto-approves every tool, `plan` is read-only.',
|
|
65
|
-
kind: 'enum',
|
|
66
|
-
values: ['default', 'auto_edit', 'yolo', 'plan'],
|
|
67
|
-
default: 'default',
|
|
68
|
-
// `plan` is read-only and `default` prompts, so neither widens anything.
|
|
69
|
-
unsafeValues: ['auto_edit', 'yolo']
|
|
70
58
|
}
|
|
71
59
|
],
|
|
72
60
|
hasRemoteControl: false,
|
package/lib/providers/kimi.js
CHANGED
|
@@ -122,7 +122,6 @@ export const descriptor = {
|
|
|
122
122
|
id: 'yoloMode',
|
|
123
123
|
title: 'YOLO mode',
|
|
124
124
|
description: 'When enabled, newly spawned and resumed sessions are launched with `--yolo`. Kimi auto-approves regular tool calls without asking - only enable in workspaces you trust.',
|
|
125
|
-
kind: 'boolean',
|
|
126
125
|
default: false,
|
|
127
126
|
menuLabel: 'YOLO'
|
|
128
127
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab_ai_code_assistants_extension",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
4
4
|
"description": "Jupyterlab extension to replace individual jupyterlab_*_code_extension and integrate into one, that offers joint capability, joint settings page and individual right toolbar panels per code assistant, with the ability in settings to decide which code assistant support is on (default - all are enabled)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
package/schema/plugin.json
CHANGED
|
@@ -69,13 +69,6 @@
|
|
|
69
69
|
"description": "When enabled, newly spawned and resumed sessions are launched with `--yolo`. Gemini auto-approves every tool call without asking - only enable in workspaces you trust.",
|
|
70
70
|
"default": false
|
|
71
71
|
},
|
|
72
|
-
"providers.gemini.approvalMode": {
|
|
73
|
-
"type": "string",
|
|
74
|
-
"title": "Gemini: Approval mode",
|
|
75
|
-
"description": "Approval ladder passed as `--approval-mode` when set to a value other than the default. A non-default value here takes precedence over the YOLO switch; only the forced YOLO menu action overrides it. `default` prompts for approval, `auto_edit` auto-approves edit tools, `yolo` auto-approves every tool, `plan` is read-only.",
|
|
76
|
-
"default": "default",
|
|
77
|
-
"enum": ["default", "auto_edit", "yolo", "plan"]
|
|
78
|
-
},
|
|
79
72
|
"providers.kimi.enabled": {
|
|
80
73
|
"type": "boolean",
|
|
81
74
|
"title": "Kimi: enabled",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Launch-mode
|
|
2
|
+
* Launch-mode resolution.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Every mode is a boolean skip-approval switch in the assistant's own
|
|
5
|
+
* terminology (DEF-111 retired the one enum ladder, Gemini's `approvalMode`),
|
|
6
|
+
* and the payload carries only one token: `force` wins outright, otherwise
|
|
7
|
+
* the first switch that is on fires.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { resolveLaunchMode, resolvedLaunchModeEntry } from '../core/modes';
|
|
@@ -15,44 +15,33 @@ const MODES: ILaunchMode[] = [
|
|
|
15
15
|
id: 'yoloMode',
|
|
16
16
|
title: 'YOLO',
|
|
17
17
|
description: 'Boolean switch.',
|
|
18
|
-
kind: 'boolean',
|
|
19
18
|
default: false,
|
|
20
19
|
// Carrying a menu label IS a boolean mode's declaration that it skips
|
|
21
20
|
// approval - it is what puts a variant in the menu.
|
|
22
21
|
menuLabel: 'YOLO'
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
id: 'approvalMode',
|
|
26
|
-
title: 'Approval',
|
|
27
|
-
description: 'Enum ladder.',
|
|
28
|
-
kind: 'enum',
|
|
29
|
-
values: ['default', 'plan', 'yolo'],
|
|
30
|
-
default: 'default',
|
|
31
|
-
unsafeValues: ['yolo']
|
|
32
22
|
}
|
|
33
23
|
];
|
|
34
24
|
|
|
35
25
|
describe('resolveLaunchMode', () => {
|
|
36
|
-
it('
|
|
37
|
-
expect(
|
|
38
|
-
resolveLaunchMode(MODES, { yoloMode: true, approvalMode: 'plan' })
|
|
39
|
-
).toEqual('approvalMode=plan');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('sends the boolean while every enum sits at its default', () => {
|
|
43
|
-
expect(
|
|
44
|
-
resolveLaunchMode(MODES, { yoloMode: true, approvalMode: 'default' })
|
|
45
|
-
).toEqual('yoloMode');
|
|
26
|
+
it('sends the boolean that is on', () => {
|
|
27
|
+
expect(resolveLaunchMode(MODES, { yoloMode: true })).toEqual('yoloMode');
|
|
46
28
|
});
|
|
47
29
|
|
|
48
30
|
it('sends nothing when no mode is set', () => {
|
|
49
31
|
expect(resolveLaunchMode(MODES, {})).toBeUndefined();
|
|
32
|
+
expect(resolveLaunchMode(MODES, { yoloMode: false })).toBeUndefined();
|
|
50
33
|
});
|
|
51
34
|
|
|
52
35
|
it('forces the requested mode whatever the settings say', () => {
|
|
36
|
+
expect(resolveLaunchMode(MODES, {}, 'yoloMode')).toEqual('yoloMode');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('ignores a token from a retired setting', () => {
|
|
40
|
+
// A stale `approvalMode` value in saved settings is not a boolean that is
|
|
41
|
+
// on - it must never become a launch token (DEF-111).
|
|
53
42
|
expect(
|
|
54
|
-
resolveLaunchMode(MODES, { approvalMode: 'plan' }
|
|
55
|
-
).
|
|
43
|
+
resolveLaunchMode(MODES, { approvalMode: 'plan' } as any)
|
|
44
|
+
).toBeUndefined();
|
|
56
45
|
});
|
|
57
46
|
});
|
|
58
47
|
|
|
@@ -60,8 +49,7 @@ describe('resolvedLaunchModeEntry', () => {
|
|
|
60
49
|
// What the panel marks its PLAIN menu items from. Deciding the warning glyph
|
|
61
50
|
// from the menu's `force` argument instead leaves the entry the user
|
|
62
51
|
// actually clicks unmarked, while marking a forced duplicate that builds the
|
|
63
|
-
// identical launch.
|
|
64
|
-
// error one level down: it warns on Gemini's `plan`, which is read-only.
|
|
52
|
+
// identical launch.
|
|
65
53
|
it('warns on a boolean skip-approval switch and names it', () => {
|
|
66
54
|
const r = resolvedLaunchModeEntry(MODES, { yoloMode: true });
|
|
67
55
|
expect(r?.mode.id).toEqual('yoloMode');
|
|
@@ -69,32 +57,8 @@ describe('resolvedLaunchModeEntry', () => {
|
|
|
69
57
|
expect(r?.unsafe).toBe(true);
|
|
70
58
|
});
|
|
71
59
|
|
|
72
|
-
it('warns on an enum value the provider declared as widening', () => {
|
|
73
|
-
const r = resolvedLaunchModeEntry(MODES, { approvalMode: 'yolo' });
|
|
74
|
-
expect(r?.mode.id).toEqual('approvalMode');
|
|
75
|
-
expect(r?.label).toEqual('yolo');
|
|
76
|
-
expect(r?.unsafe).toBe(true);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('does NOT warn on an enum value that is stricter than the default', () => {
|
|
80
|
-
// `plan` is read-only. Marking it like `yolo` says nothing about either.
|
|
81
|
-
const r = resolvedLaunchModeEntry(MODES, { approvalMode: 'plan' });
|
|
82
|
-
expect(r?.mode.id).toEqual('approvalMode');
|
|
83
|
-
expect(r?.label).toEqual('plan');
|
|
84
|
-
expect(r?.unsafe).toBe(false);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('names the mode that actually wins, not both', () => {
|
|
88
|
-
expect(
|
|
89
|
-
resolvedLaunchModeEntry(MODES, { yoloMode: true, approvalMode: 'plan' })
|
|
90
|
-
?.mode.id
|
|
91
|
-
).toEqual('approvalMode');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
60
|
it('names nothing for a launch that carries no mode', () => {
|
|
95
61
|
expect(resolvedLaunchModeEntry(MODES, {})).toBeNull();
|
|
96
|
-
expect(
|
|
97
|
-
resolvedLaunchModeEntry(MODES, { approvalMode: 'default' })
|
|
98
|
-
).toBeNull();
|
|
62
|
+
expect(resolvedLaunchModeEntry(MODES, { yoloMode: false })).toBeNull();
|
|
99
63
|
});
|
|
100
64
|
});
|
|
@@ -42,7 +42,7 @@ jest.mock('../core/request', () => ({
|
|
|
42
42
|
}));
|
|
43
43
|
|
|
44
44
|
import { TAB_COLOUR_IDS, fnv1aColour } from '../core/colour';
|
|
45
|
-
import { addIcon, branchIcon, shieldIcon } from '../core/icons';
|
|
45
|
+
import { addIcon, branchIcon, cleanupIcon, shieldIcon } from '../core/icons';
|
|
46
46
|
import { AssistantSessionsPanel, commandId } from '../core/panel';
|
|
47
47
|
import { requestProvider } from '../core/request';
|
|
48
48
|
import { IProviderDescriptor, ISession } from '../core/types';
|
|
@@ -66,7 +66,6 @@ const DESCRIPTOR: IProviderDescriptor = {
|
|
|
66
66
|
id: 'skip',
|
|
67
67
|
title: 'Skip approvals',
|
|
68
68
|
description: 'Run without asking.',
|
|
69
|
-
kind: 'boolean',
|
|
70
69
|
default: false,
|
|
71
70
|
menuLabel: 'Skip Permissions'
|
|
72
71
|
}
|
|
@@ -277,16 +276,14 @@ describe('the + button names and marks the launch it performs', () => {
|
|
|
277
276
|
expect(newBtn().title).not.toContain('(Skip Permissions)');
|
|
278
277
|
});
|
|
279
278
|
|
|
280
|
-
it('DEF-
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
//
|
|
279
|
+
it('DEF-112 - the glyph is always +, one svg at a time', () => {
|
|
280
|
+
// The shield marks the menu entries that skip approval; the button that
|
|
281
|
+
// offers or launches stays neutral in every mode state. The mode is still
|
|
282
|
+
// named in the title, per DEF-36 above.
|
|
284
283
|
expect(glyphs()).toEqual([addIcon.name]);
|
|
285
284
|
panel.setModes({ skip: true });
|
|
286
285
|
expect((panel as any)._visibleVariantCount()).toEqual(0);
|
|
287
|
-
expect(glyphs()).toEqual([
|
|
288
|
-
// `LabIcon.element` appends, so a button that is not cleared collects one
|
|
289
|
-
// svg per settings change.
|
|
286
|
+
expect(glyphs()).toEqual([addIcon.name]);
|
|
290
287
|
panel.setModes({ skip: false });
|
|
291
288
|
expect(glyphs()).toEqual([addIcon.name]);
|
|
292
289
|
});
|
|
@@ -346,7 +343,7 @@ describe('DEF-35 / DEF-39 - the branch entry with no visible variants', () => {
|
|
|
346
343
|
|
|
347
344
|
// --------------------------------------------------------------- DEF-40
|
|
348
345
|
|
|
349
|
-
describe('DEF-40 - the
|
|
346
|
+
describe('DEF-40 - the shield glyph matches its siblings in size and colour', () => {
|
|
350
347
|
const svg = (svgstr: string): SVGElement =>
|
|
351
348
|
new DOMParser().parseFromString(svgstr, 'image/svg+xml')
|
|
352
349
|
.documentElement as unknown as SVGElement;
|
|
@@ -364,23 +361,27 @@ describe('DEF-40 - the warning glyph is sized and coloured to stand out', () =>
|
|
|
364
361
|
}
|
|
365
362
|
});
|
|
366
363
|
|
|
367
|
-
it('paints in jp-
|
|
368
|
-
//
|
|
369
|
-
//
|
|
364
|
+
it('paints in the standard jp-icon3, like its siblings', () => {
|
|
365
|
+
// DEF-40's orange (`jp-icon-warn0`) was reversed by user ruling
|
|
366
|
+
// 2026-08-11: the shield SHAPE alone marks the difference, the colour
|
|
367
|
+
// stays the theme's neutral like every other icon in the menu.
|
|
370
368
|
const path = svg(shieldIcon.svgstr).querySelector('path')!;
|
|
371
|
-
expect(path.getAttribute('class')).toEqual('jp-
|
|
372
|
-
expect(shieldIcon.svgstr).not.toContain('jp-
|
|
369
|
+
expect(path.getAttribute('class')).toEqual('jp-icon3');
|
|
370
|
+
expect(shieldIcon.svgstr).not.toContain('jp-icon-warn0');
|
|
373
371
|
});
|
|
374
372
|
});
|
|
375
373
|
|
|
376
374
|
// ------------------------------------------------- DEF-45 / DEF-46 (lane A)
|
|
377
375
|
|
|
378
376
|
describe('DEF-45 - both destructive menu entries are marked alike', () => {
|
|
379
|
-
it('gives Clean Up Parallel Sessions the
|
|
377
|
+
it('gives Clean Up Parallel Sessions the sweep variant of the trash glyph', () => {
|
|
378
|
+
// Same family as Remove's plain trash - marked alike, per DEF-45 - but
|
|
379
|
+
// the bulk variant, because this one deletes several at once (user
|
|
380
|
+
// direction 2026-08-11; it no longer shares the unsafe-launch shield).
|
|
380
381
|
const commands = (panel as any)._commands;
|
|
381
382
|
expect(
|
|
382
383
|
commands.icon(commandId(DESCRIPTOR.id, 'cleanup-parallel'), {})
|
|
383
|
-
).toBe(
|
|
384
|
+
).toBe(cleanupIcon);
|
|
384
385
|
expect(commands.icon(commandId(DESCRIPTOR.id, 'remove'), {})).toBeDefined();
|
|
385
386
|
});
|
|
386
387
|
});
|
|
@@ -79,13 +79,8 @@ describe('the providers barrel', () => {
|
|
|
79
79
|
expect(mode.id.trim()).toBeTruthy();
|
|
80
80
|
expect(mode.title.trim()).toBeTruthy();
|
|
81
81
|
expect(mode.description.trim()).toBeTruthy();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
expect(mode.default).toBe(false);
|
|
85
|
-
} else {
|
|
86
|
-
expect(mode.values).toBeDefined();
|
|
87
|
-
expect(mode.values).toContain(mode.default as string);
|
|
88
|
-
}
|
|
82
|
+
// Every unsafe switch is off by default, without exception.
|
|
83
|
+
expect(mode.default).toBe(false);
|
|
89
84
|
}
|
|
90
85
|
const ids = descriptor.launchModes.map(m => m.id);
|
|
91
86
|
expect(new Set(ids).size).toEqual(ids.length);
|
package/src/core/icons.ts
CHANGED
|
@@ -48,19 +48,30 @@ export const removeIcon = new LabIcon({
|
|
|
48
48
|
svgstr: removeSvgStr
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
// `Clean Up Parallel Sessions` - deletes several at once, so it wears the
|
|
52
|
+
// sweep variant of the trash glyph (Material "delete_sweep"), same family as
|
|
53
|
+
// `Remove from ...`'s plain trash but visibly the bulk one (user direction
|
|
54
|
+
// 2026-08-11, replacing the shared danger marker it wore under DEF-45).
|
|
55
|
+
const cleanupSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
|
|
56
|
+
<path class="jp-icon3" fill="#616161" d="M15 16h4v2h-4zm0-8h7v2h-7zm0 4h6v2h-6zM3 18c0 1.1.9 2 2 2h6c1.1 0 2-.9 2-2V8H3v10zM14 5h-3l-1-1H6L5 5H2v2h12z"/>
|
|
57
|
+
</svg>`;
|
|
58
|
+
|
|
59
|
+
export const cleanupIcon = new LabIcon({
|
|
60
|
+
name: `${ICON_PREFIX}:cleanup`,
|
|
61
|
+
svgstr: cleanupSvgStr
|
|
62
|
+
});
|
|
63
|
+
|
|
51
64
|
// Marks the unsafe launch variants. The shield is the established marker for
|
|
52
65
|
// an action that touches the permission system (the UAC pattern), and it is
|
|
53
66
|
// what the reference implementation used; Material "shield" half-fill, kept
|
|
54
67
|
// per user direction 2026-08-11 over the earlier caution triangle.
|
|
55
68
|
//
|
|
56
|
-
// 16x16
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
// the
|
|
60
|
-
// whole job is to say "this differs" was the least conspicuous in the menu
|
|
61
|
-
// (DEF-40, and the reason the reference's 13x13 grey is not copied verbatim).
|
|
69
|
+
// 16x16 like its siblings (DEF-40's size half - authored at 13x13 it
|
|
70
|
+
// rendered smaller than every icon beside it), and in the standard `jp-icon3`
|
|
71
|
+
// like them too: the user ruled 2026-08-11 that the shield shape alone marks
|
|
72
|
+
// the difference, reversing DEF-40's colour half (the orange `jp-icon-warn0`).
|
|
62
73
|
const shieldSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
|
|
63
|
-
<path class="jp-
|
|
74
|
+
<path class="jp-icon3" fill="#616161" d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/>
|
|
64
75
|
</svg>`;
|
|
65
76
|
|
|
66
77
|
export const shieldIcon = new LabIcon({
|
package/src/core/modes.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// Launch-mode resolution: which single mode token, if any, one launch carries.
|
|
2
2
|
//
|
|
3
3
|
// Its own module because the panel is the only caller but not the only reader -
|
|
4
|
-
// the
|
|
5
|
-
//
|
|
4
|
+
// the rule that one launch carries at most one mode is worth asserting on its
|
|
5
|
+
// own, and this file pulls in no JupyterLab surface.
|
|
6
6
|
|
|
7
7
|
import { ILaunchMode } from './types';
|
|
8
8
|
|
|
9
9
|
/** A resolved launch mode, with what to call it and whether to warn. */
|
|
10
10
|
export interface IResolvedLaunchMode {
|
|
11
11
|
mode: ILaunchMode;
|
|
12
|
-
/** What the menu should call it - the
|
|
12
|
+
/** What the menu should call it - the mode's own label. */
|
|
13
13
|
label: string;
|
|
14
14
|
/** Whether this widens what the assistant may do without asking. */
|
|
15
15
|
unsafe: boolean;
|
|
@@ -20,11 +20,8 @@ export interface IResolvedLaunchMode {
|
|
|
20
20
|
*
|
|
21
21
|
* The payload carries at most one mode, because the server maps one token to
|
|
22
22
|
* one flag. `force` wins outright - that is what the menu's variant entries
|
|
23
|
-
* do. Otherwise
|
|
24
|
-
*
|
|
25
|
-
* left on must not silently invert it. A boolean fires only while every enum
|
|
26
|
-
* mode sits at its default. An enum at its default sends nothing - it is what
|
|
27
|
-
* the CLI would do unasked.
|
|
23
|
+
* do. Otherwise the first switch that is on fires. Every mode is a boolean
|
|
24
|
+
* skip-approval switch, so "on" always means the same thing.
|
|
28
25
|
*/
|
|
29
26
|
export function resolveLaunchMode(
|
|
30
27
|
launchModes: ILaunchMode[],
|
|
@@ -35,18 +32,7 @@ export function resolveLaunchMode(
|
|
|
35
32
|
return force;
|
|
36
33
|
}
|
|
37
34
|
for (const mode of launchModes) {
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
mode.kind !== 'boolean' &&
|
|
41
|
-
typeof value === 'string' &&
|
|
42
|
-
value &&
|
|
43
|
-
value !== mode.default
|
|
44
|
-
) {
|
|
45
|
-
return `${mode.id}=${value}`;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
for (const mode of launchModes) {
|
|
49
|
-
if (mode.kind === 'boolean' && values[mode.id] === true) {
|
|
35
|
+
if (values[mode.id] === true) {
|
|
50
36
|
return mode.id;
|
|
51
37
|
}
|
|
52
38
|
}
|
|
@@ -71,23 +57,15 @@ export function resolvedLaunchModeEntry(
|
|
|
71
57
|
if (!token) {
|
|
72
58
|
return null;
|
|
73
59
|
}
|
|
74
|
-
const
|
|
75
|
-
const mode = launchModes.find(m => m.id === id);
|
|
60
|
+
const mode = launchModes.find(m => m.id === token);
|
|
76
61
|
if (!mode) {
|
|
77
62
|
return null;
|
|
78
63
|
}
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
// to a mode and is stricter than the default, so "resolved at all" is the
|
|
82
|
-
// wrong question to ask about danger.
|
|
83
|
-
const unsafe = value
|
|
84
|
-
? (mode.unsafeValues ?? []).indexOf(value) !== -1
|
|
85
|
-
: !!mode.menuLabel;
|
|
86
|
-
// Name the VALUE where there is one - three enum values sharing the mode's
|
|
87
|
-
// title render as one indistinguishable label.
|
|
64
|
+
// Carrying a menu label IS a boolean mode's declaration that it skips
|
|
65
|
+
// approval - it is what puts a variant in the menu.
|
|
88
66
|
return {
|
|
89
67
|
mode,
|
|
90
|
-
label:
|
|
91
|
-
unsafe
|
|
68
|
+
label: mode.menuLabel ?? mode.title,
|
|
69
|
+
unsafe: !!mode.menuLabel
|
|
92
70
|
};
|
|
93
71
|
}
|
package/src/core/panel.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { ColourStore } from './colour';
|
|
|
26
26
|
import {
|
|
27
27
|
addIcon,
|
|
28
28
|
branchIcon,
|
|
29
|
+
cleanupIcon,
|
|
29
30
|
filterIcon,
|
|
30
31
|
providerIcon,
|
|
31
32
|
refreshIcon,
|
|
@@ -275,11 +276,9 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
275
276
|
setModes(modes: Record<string, boolean | string>): void {
|
|
276
277
|
this._modes = { ...modes };
|
|
277
278
|
// Menu labels are lazy and re-read this on every open; the button's title
|
|
278
|
-
// is a written string, so it has to be rewritten here.
|
|
279
|
+
// is a written string, so it has to be rewritten here. The glyph needs no
|
|
280
|
+
// such hook - it is always "+", whatever mode is armed (DEF-112).
|
|
279
281
|
this._renameNewButton?.();
|
|
280
|
-
// Same for its glyph - a menu item re-reads its icon on every open, a
|
|
281
|
-
// painted button does not.
|
|
282
|
-
this._repaintNewIcon?.();
|
|
283
282
|
}
|
|
284
283
|
|
|
285
284
|
setColouredTabs(on: boolean): void {
|
|
@@ -343,26 +342,11 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
343
342
|
// after turning an approval-free mode on (DEF-38).
|
|
344
343
|
this._renameNewButton = nameNewButton;
|
|
345
344
|
nameNewButton();
|
|
346
|
-
// The glyph
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
-
// wearing no warning at all.
|
|
352
|
-
const paintNewIcon = (): void => {
|
|
353
|
-
const icon =
|
|
354
|
-
(this._visibleVariantCount() === 0 ? this._variantIcon() : undefined) ??
|
|
355
|
-
addIcon;
|
|
356
|
-
// `LabIcon.element` appends into the container, so the previous glyph
|
|
357
|
-
// has to go or the button would collect one svg per settings change.
|
|
358
|
-
newBtn.replaceChildren();
|
|
359
|
-
icon.element({ container: newBtn });
|
|
360
|
-
};
|
|
361
|
-
// Held for the same reason the title is: the shell is built before
|
|
362
|
-
// settings arrive, so what is drawn here can only say "no mode in force"
|
|
363
|
-
// and `setModes` has to repaint it (DEF-38).
|
|
364
|
-
this._repaintNewIcon = paintNewIcon;
|
|
365
|
-
paintNewIcon();
|
|
345
|
+
// The glyph is always "+", whatever mode is armed (DEF-112): the shield
|
|
346
|
+
// marks the menu ENTRIES that skip approval, while the button that offers
|
|
347
|
+
// or launches stays neutral - an armed mode is still named in the title
|
|
348
|
+
// above, so the launch never goes unnamed.
|
|
349
|
+
addIcon.element({ container: newBtn });
|
|
366
350
|
newBtn.addEventListener('click', () => {
|
|
367
351
|
// With a mode in force the variant items suppress themselves, leaving a
|
|
368
352
|
// dropdown with one entry - two clicks for its only outcome. Do it.
|
|
@@ -1927,12 +1911,10 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
1927
1911
|
return commandId(this._descriptor.id, action);
|
|
1928
1912
|
}
|
|
1929
1913
|
|
|
1930
|
-
/**
|
|
1914
|
+
/** Launch modes that produce a menu variant - the assistant's own
|
|
1931
1915
|
* skip-approval switch, under the assistant's own name. */
|
|
1932
1916
|
private get _variantModes(): ILaunchMode[] {
|
|
1933
|
-
return this._descriptor.launchModes.filter(
|
|
1934
|
-
m => m.kind === 'boolean' && !!m.menuLabel
|
|
1935
|
-
);
|
|
1917
|
+
return this._descriptor.launchModes.filter(m => !!m.menuLabel);
|
|
1936
1918
|
}
|
|
1937
1919
|
|
|
1938
1920
|
/** The launch mode a PLAIN action resolves to right now, or null.
|
|
@@ -2170,8 +2152,9 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2170
2152
|
// Marked, like `Remove from ...` below it. The two share the group the
|
|
2171
2153
|
// separator was added to protect, and both delete history that cannot be
|
|
2172
2154
|
// recovered; leaving this one bare made the pair read as one dangerous
|
|
2173
|
-
// item beside an ordinary one (DEF-45).
|
|
2174
|
-
|
|
2155
|
+
// item beside an ordinary one (DEF-45). The sweep variant of the trash
|
|
2156
|
+
// glyph, because this one deletes several at once.
|
|
2157
|
+
icon: cleanupIcon,
|
|
2175
2158
|
isVisible: () => (active()?.extra_sessions ?? 0) > 0,
|
|
2176
2159
|
execute: () => {
|
|
2177
2160
|
const s = active();
|
|
@@ -2654,8 +2637,6 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2654
2637
|
private _newSessionMenu!: Menu;
|
|
2655
2638
|
/** Re-writes the `+` button's title. Set once the shell exists. */
|
|
2656
2639
|
private _renameNewButton: (() => void) | null = null;
|
|
2657
|
-
/** Re-draws the `+` button's glyph. Set once the shell exists. */
|
|
2658
|
-
private _repaintNewIcon: (() => void) | null = null;
|
|
2659
2640
|
private _lastBranches: IBranch[] = [];
|
|
2660
2641
|
private _lastBranchesCurrent = '';
|
|
2661
2642
|
private _colouredTabs = true;
|
package/src/core/types.ts
CHANGED
|
@@ -27,9 +27,8 @@ export type ForkStrategy = 'native-flag' | 'native-command' | 'server-copy';
|
|
|
27
27
|
export type ColourSource = 'native' | 'derived' | 'none';
|
|
28
28
|
|
|
29
29
|
/** One launch-behaviour setting of an assistant, in that assistant's own
|
|
30
|
-
* terminology.
|
|
31
|
-
*
|
|
32
|
-
* `enum` modes are settings-only choices passed through on every launch. */
|
|
30
|
+
* terminology. A launch mode is a boolean skip-approval switch: it gets a
|
|
31
|
+
* settings toggle (off by default) AND a context-menu launch variant. */
|
|
33
32
|
export interface ILaunchMode {
|
|
34
33
|
/** Settings key under `providers.<id>.` and launch-payload key. Uses the
|
|
35
34
|
* assistant's own terminology, never a normalised core name. */
|
|
@@ -38,24 +37,12 @@ export interface ILaunchMode {
|
|
|
38
37
|
title: string;
|
|
39
38
|
/** Settings description. */
|
|
40
39
|
description: string;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
values?: string[];
|
|
44
|
-
/** Default value. Boolean modes default to `false` without exception. */
|
|
45
|
-
default: boolean | string;
|
|
40
|
+
/** Default value - `false` without exception. */
|
|
41
|
+
default: boolean;
|
|
46
42
|
/** Menu label for the launch variant this mode produces, e.g.
|
|
47
|
-
* `Skip Permissions`.
|
|
43
|
+
* `Skip Permissions`. Carrying one IS the mode's declaration that it skips
|
|
44
|
+
* approval - it is what puts a variant in the menu. */
|
|
48
45
|
menuLabel?: string;
|
|
49
|
-
/** `enum` modes only: the values that WIDEN what the assistant may do
|
|
50
|
-
* without asking. The panel warns on these and names the value; every other
|
|
51
|
-
* value is a settings choice and is marked as one.
|
|
52
|
-
*
|
|
53
|
-
* Declared per provider rather than inferred, because "a mode resolved at
|
|
54
|
-
* all" is not the same question: Gemini's `plan` is read-only - stricter
|
|
55
|
-
* than the default - and marking it the same as `yolo` says nothing about
|
|
56
|
-
* either. A boolean mode needs no entry here; carrying a `menuLabel` is
|
|
57
|
-
* already its declaration that it skips approval. */
|
|
58
|
-
unsafeValues?: string[];
|
|
59
46
|
}
|
|
60
47
|
|
|
61
48
|
/** Everything the core needs to know about one assistant. Pure data - no
|
package/src/providers/claude.ts
CHANGED
|
@@ -57,7 +57,6 @@ export const descriptor: IProviderDescriptor = {
|
|
|
57
57
|
title: 'Dangerously skip permissions',
|
|
58
58
|
description:
|
|
59
59
|
'When enabled, newly spawned and resumed sessions are launched with `--dangerously-skip-permissions`. Bypasses every Claude Code permission prompt - only enable in sandboxes you trust.',
|
|
60
|
-
kind: 'boolean',
|
|
61
60
|
default: false,
|
|
62
61
|
menuLabel: 'Skip Permissions'
|
|
63
62
|
}
|
package/src/providers/codex.ts
CHANGED
|
@@ -54,7 +54,6 @@ export const descriptor: IProviderDescriptor = {
|
|
|
54
54
|
title: 'Bypass Codex approvals and sandbox',
|
|
55
55
|
description:
|
|
56
56
|
'When enabled, newly spawned and resumed sessions are launched with `--dangerously-bypass-approvals-and-sandbox`. Bypasses every Codex approval prompt and sandbox - only enable in environments you trust.',
|
|
57
|
-
kind: 'boolean',
|
|
58
57
|
default: false,
|
|
59
58
|
menuLabel: 'Bypass Approvals'
|
|
60
59
|
}
|
package/src/providers/gemini.ts
CHANGED
|
@@ -32,11 +32,10 @@ const geminiSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 65 65
|
|
|
32
32
|
<path class="jp-icon3" fill="#616161" d="M32.447 0c.68 0 1.273.465 1.439 1.125a38.904 38.904 0 001.999 5.905c2.152 5 5.105 9.376 8.854 13.125 3.751 3.75 8.126 6.703 13.125 8.855a38.98 38.98 0 005.906 1.999c.66.166 1.124.758 1.124 1.438 0 .68-.464 1.273-1.125 1.439a38.902 38.902 0 00-5.905 1.999c-5 2.152-9.375 5.105-13.125 8.854-3.749 3.751-6.702 8.126-8.854 13.125a38.973 38.973 0 00-2 5.906 1.485 1.485 0 01-1.438 1.124c-.68 0-1.272-.464-1.438-1.125a38.913 38.913 0 00-2-5.905c-2.151-5-5.103-9.375-8.854-13.125-3.75-3.749-8.125-6.702-13.125-8.854a38.973 38.973 0 00-5.905-2A1.485 1.485 0 010 32.448c0-.68.465-1.272 1.125-1.438a38.903 38.903 0 005.905-2c5-2.151 9.376-5.104 13.125-8.854 3.75-3.749 6.703-8.125 8.855-13.125a38.972 38.972 0 001.999-5.905A1.485 1.485 0 0132.447 0z"/>
|
|
33
33
|
</svg>`;
|
|
34
34
|
|
|
35
|
-
// Gemini's own
|
|
36
|
-
// terminology rather than normalised, because
|
|
35
|
+
// Gemini's own name for its approval switch. Kept in the assistant's
|
|
36
|
+
// terminology rather than normalised, because it is both the settings key
|
|
37
37
|
// under `providers.gemini.` and the mode token the server maps to a CLI flag.
|
|
38
38
|
const YOLO_MODE = 'yoloMode';
|
|
39
|
-
const APPROVAL_MODE = 'approvalMode';
|
|
40
39
|
|
|
41
40
|
export const descriptor: IProviderDescriptor = {
|
|
42
41
|
id: 'gemini',
|
|
@@ -60,20 +59,8 @@ export const descriptor: IProviderDescriptor = {
|
|
|
60
59
|
title: 'YOLO mode',
|
|
61
60
|
description:
|
|
62
61
|
'When enabled, newly spawned and resumed sessions are launched with `--yolo`. Gemini auto-approves every tool call without asking - only enable in workspaces you trust.',
|
|
63
|
-
kind: 'boolean',
|
|
64
62
|
default: false,
|
|
65
63
|
menuLabel: 'YOLO'
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
id: APPROVAL_MODE,
|
|
69
|
-
title: 'Approval mode',
|
|
70
|
-
description:
|
|
71
|
-
'Approval ladder passed as `--approval-mode` when set to a value other than the default. A non-default value here takes precedence over the YOLO switch; only the forced YOLO menu action overrides it. `default` prompts for approval, `auto_edit` auto-approves edit tools, `yolo` auto-approves every tool, `plan` is read-only.',
|
|
72
|
-
kind: 'enum',
|
|
73
|
-
values: ['default', 'auto_edit', 'yolo', 'plan'],
|
|
74
|
-
default: 'default',
|
|
75
|
-
// `plan` is read-only and `default` prompts, so neither widens anything.
|
|
76
|
-
unsafeValues: ['auto_edit', 'yolo']
|
|
77
64
|
}
|
|
78
65
|
],
|
|
79
66
|
hasRemoteControl: false,
|
package/src/providers/kimi.ts
CHANGED
|
@@ -135,7 +135,6 @@ export const descriptor: IProviderDescriptor = {
|
|
|
135
135
|
title: 'YOLO mode',
|
|
136
136
|
description:
|
|
137
137
|
'When enabled, newly spawned and resumed sessions are launched with `--yolo`. Kimi auto-approves regular tool calls without asking - only enable in workspaces you trust.',
|
|
138
|
-
kind: 'boolean',
|
|
139
138
|
default: false,
|
|
140
139
|
menuLabel: 'YOLO'
|
|
141
140
|
}
|