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 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 and approval modes; unsafe variants carry a warning glyph
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
@@ -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 and `jp-icon-warn0`, like every other icon in these menus and unlike
46
- // them in colour: authored at 13x13 in `jp-icon3` it rendered smaller than
47
- // its neutral siblings AND in the identical grey, measured rgb(97,97,97) in
48
- // the light theme and rgb(189,189,189) in the dark one - the one glyph whose
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-icon-warn0" fill="#f57c00" 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"/>
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`,
@@ -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 value, or the mode's own label. */
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 an enum mode moved off its default takes precedence over any
16
- * boolean switch: the enum is the explicit statement of intent, so a boolean
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 precedence between a boolean switch and an enum ladder is a rule worth
5
- // asserting on its own, and this file pulls in no JupyterLab surface.
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 an enum mode moved off its default takes precedence over any
12
- * boolean switch: the enum is the explicit statement of intent, so a boolean
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
- const value = values[mode.id];
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, _b;
36
+ var _a;
49
37
  const token = resolveLaunchMode(launchModes, values);
50
38
  if (!token) {
51
39
  return null;
52
40
  }
53
- const [id, value] = token.split('=');
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
- // A boolean mode with a menu label IS the assistant's skip-approval switch;
59
- // an enum only widens on the values its provider declared. `plan` resolves
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: (_b = value !== null && value !== void 0 ? value : mode.menuLabel) !== null && _b !== void 0 ? _b : mode.title,
70
- unsafe
49
+ label: (_a = mode.menuLabel) !== null && _a !== void 0 ? _a : mode.title,
50
+ unsafe: !!mode.menuLabel
71
51
  };
72
52
  }
@@ -188,7 +188,7 @@ export declare class AssistantSessionsPanel extends Widget {
188
188
  private _setRefreshSpinning;
189
189
  private _setActiveRow;
190
190
  private _cmd;
191
- /** Boolean launch modes that produce a menu variant - the assistant's own
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, _b;
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 follows the same rule the menu entries follow: where a mode
286
- // WIDENS what runs without asking, the mode's warning glyph wins over the
287
- // action's own (DEF-35). It matters most here - with a mode in force the
288
- // variants suppress themselves and this button launches on click, so a
289
- // plain "+" would be the only one-click approval-free launch in the panel
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
- /** Boolean launch modes that produce a menu variant - the assistant's own
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 => m.kind === 'boolean' && !!m.menuLabel);
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
- icon: shieldIcon,
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();
@@ -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. `boolean` modes are the unsafe/skip-approval switches: they get
23
- * a settings toggle (off by default) AND a context-menu launch variant.
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
- kind: 'boolean' | 'enum';
34
- /** Allowed values, `enum` modes only. */
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`. Boolean modes only; absent means settings-only. */
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
@@ -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
  }
@@ -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
  }
@@ -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 names for its two launch surfaces. Kept in the assistant's
32
- // terminology rather than normalised, because each is both the settings key
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,
@@ -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.19",
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",
@@ -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 precedence.
2
+ * Launch-mode resolution.
3
3
  *
4
- * A provider can expose both a boolean switch and an enum ladder over the same
5
- * approval surface, and the payload carries only one token. An enum moved off
6
- * its default is the user's explicit statement, so it has to beat a boolean
7
- * left on - otherwise a read-only ladder setting launches wide open.
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('lets a non-default enum beat a boolean that is on', () => {
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' }, 'yoloMode')
55
- ).toEqual('yoloMode');
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. Deciding it from "a mode resolved at all" is the same
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-35 / DEF-38 - the glyph follows the mode, one svg at a time', () => {
281
- // With the mode in force every variant suppresses itself, so this button
282
- // stops dropping a menu and launches on click - a plain + would be the one
283
- // one-click approval-free launch in the panel wearing no warning.
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([shieldIcon.name]);
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 warning glyph is sized and coloured to stand out', () => {
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-icon-warn0 and not the neutral grey', () => {
368
- // `jp-icon3` is the theme's neutral: in it the glyph measured the same
369
- // rgb(97,97,97) as the icons it was supposed to differ from.
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-icon-warn0');
372
- expect(shieldIcon.svgstr).not.toContain('jp-icon3');
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 same glyph Remove carries', () => {
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(shieldIcon);
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
- if (mode.kind === 'boolean') {
83
- // Every unsafe switch is off by default, without exception.
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 and `jp-icon-warn0`, like every other icon in these menus and unlike
57
- // them in colour: authored at 13x13 in `jp-icon3` it rendered smaller than
58
- // its neutral siblings AND in the identical grey, measured rgb(97,97,97) in
59
- // the light theme and rgb(189,189,189) in the dark one - the one glyph whose
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-icon-warn0" fill="#f57c00" 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"/>
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 precedence between a boolean switch and an enum ladder is a rule worth
5
- // asserting on its own, and this file pulls in no JupyterLab surface.
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 value, or the mode's own label. */
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 an enum mode moved off its default takes precedence over any
24
- * boolean switch: the enum is the explicit statement of intent, so a boolean
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
- const value = values[mode.id];
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 [id, value] = token.split('=');
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
- // A boolean mode with a menu label IS the assistant's skip-approval switch;
80
- // an enum only widens on the values its provider declared. `plan` resolves
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: value ?? mode.menuLabel ?? mode.title,
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 follows the same rule the menu entries follow: where a mode
347
- // WIDENS what runs without asking, the mode's warning glyph wins over the
348
- // action's own (DEF-35). It matters most here - with a mode in force the
349
- // variants suppress themselves and this button launches on click, so a
350
- // plain "+" would be the only one-click approval-free launch in the panel
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
- /** Boolean launch modes that produce a menu variant - the assistant's own
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
- icon: shieldIcon,
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. `boolean` modes are the unsafe/skip-approval switches: they get
31
- * a settings toggle (off by default) AND a context-menu launch variant.
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
- kind: 'boolean' | 'enum';
42
- /** Allowed values, `enum` modes only. */
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`. Boolean modes only; absent means settings-only. */
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
@@ -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
  }
@@ -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
  }
@@ -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 names for its two launch surfaces. Kept in the assistant's
36
- // terminology rather than normalised, because each is both the settings key
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,
@@ -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
  }