jupyterlab_ai_code_assistants_extension 0.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +44 -0
  3. package/lib/core/colour.d.ts +57 -0
  4. package/lib/core/colour.js +190 -0
  5. package/lib/core/icons.d.ts +12 -0
  6. package/lib/core/icons.js +99 -0
  7. package/lib/core/modes.d.ts +13 -0
  8. package/lib/core/modes.js +36 -0
  9. package/lib/core/panel.d.ts +233 -0
  10. package/lib/core/panel.js +1673 -0
  11. package/lib/core/popup.d.ts +40 -0
  12. package/lib/core/popup.js +404 -0
  13. package/lib/core/registry.d.ts +24 -0
  14. package/lib/core/registry.js +48 -0
  15. package/lib/core/request.d.ts +25 -0
  16. package/lib/core/request.js +64 -0
  17. package/lib/core/terminals.d.ts +129 -0
  18. package/lib/core/terminals.js +344 -0
  19. package/lib/core/types.d.ts +269 -0
  20. package/lib/core/types.js +6 -0
  21. package/lib/index.d.ts +3 -0
  22. package/lib/index.js +277 -0
  23. package/lib/providers/claude.d.ts +3 -0
  24. package/lib/providers/claude.js +88 -0
  25. package/lib/providers/codex.d.ts +3 -0
  26. package/lib/providers/codex.js +79 -0
  27. package/lib/providers/gemini.d.ts +2 -0
  28. package/lib/providers/gemini.js +73 -0
  29. package/lib/providers/index.d.ts +3 -0
  30. package/lib/providers/index.js +16 -0
  31. package/lib/providers/kimi.d.ts +23 -0
  32. package/lib/providers/kimi.js +139 -0
  33. package/package.json +169 -0
  34. package/schema/plugin.json +93 -0
  35. package/src/__tests__/colour.spec.ts +153 -0
  36. package/src/__tests__/core-neutrality.spec.ts +86 -0
  37. package/src/__tests__/labels.spec.ts +89 -0
  38. package/src/__tests__/launch-mode.spec.ts +53 -0
  39. package/src/__tests__/registry.spec.ts +178 -0
  40. package/src/core/colour.ts +224 -0
  41. package/src/core/icons.ts +121 -0
  42. package/src/core/modes.ts +45 -0
  43. package/src/core/panel.ts +2020 -0
  44. package/src/core/popup.ts +475 -0
  45. package/src/core/registry.ts +60 -0
  46. package/src/core/request.ts +96 -0
  47. package/src/core/terminals.ts +446 -0
  48. package/src/core/types.ts +305 -0
  49. package/src/index.ts +356 -0
  50. package/src/providers/claude.ts +102 -0
  51. package/src/providers/codex.ts +89 -0
  52. package/src/providers/gemini.ts +80 -0
  53. package/src/providers/index.ts +21 -0
  54. package/src/providers/kimi.ts +155 -0
  55. package/style/base.css +888 -0
  56. package/style/index.css +1 -0
  57. package/style/index.js +1 -0
package/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Stellars Henson
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # jupyterlab_ai_code_assistants_extension
2
+
3
+ [![GitHub Actions](https://github.com/stellarshenson/jupyterlab_ai_code_assistants_extension/actions/workflows/build.yml/badge.svg)](https://github.com/stellarshenson/jupyterlab_ai_code_assistants_extension/actions/workflows/build.yml)
4
+ [![npm version](https://img.shields.io/npm/v/jupyterlab_ai_code_assistants_extension.svg)](https://www.npmjs.com/package/jupyterlab_ai_code_assistants_extension)
5
+ [![PyPI version](https://img.shields.io/pypi/v/jupyterlab-ai-code-assistants-extension.svg)](https://pypi.org/project/jupyterlab-ai-code-assistants-extension/)
6
+ [![Total PyPI downloads](https://static.pepy.tech/badge/jupyterlab-ai-code-assistants-extension)](https://pepy.tech/project/jupyterlab-ai-code-assistants-extension)
7
+ [![JupyterLab 4](https://img.shields.io/badge/JupyterLab-4-orange.svg)](https://jupyterlab.readthedocs.io/en/stable/)
8
+ [![Brought To You By KOLOMOLO](https://img.shields.io/badge/Brought%20To%20You%20By-KOLOMOLO-00ffff?style=flat)](https://kolomolo.com)
9
+ [![Donate PayPal](https://img.shields.io/badge/Donate-PayPal-blue?style=flat)](https://www.paypal.com/donate/?hosted_button_id=B4KPBJDLLXTSA)
10
+
11
+ One extension for every AI code assistant in JupyterLab. Replaces the separate `jupyterlab_claude_code_extension`, `jupyterlab_codex_extension` and `jupyterlab_kimi_code_extension` packages with a single install that keeps each assistant's own side panel while sharing one settings page and one session-handling core underneath.
12
+
13
+ > [!NOTE]
14
+ > Early development. The scaffolding, build lifecycle and packaging are in place; the assistant panels are being ported from the standalone extensions.
15
+
16
+ ## Features
17
+
18
+ - **One install, every assistant** - Claude Code, Codex, Kimi and Gemini supported from a single package
19
+ - **Individual right-panel per assistant** - each assistant keeps its own side panel with Favorites, Recent and All projects
20
+ - **Joint settings page** - one settings section covering all assistants, replacing three separate ones
21
+ - **Per-assistant toggles** - switch any assistant's support on or off in settings; all are enabled by default
22
+ - **Shared session core** - resume, branch, switch and clean up conversations through one common mechanism across assistants
23
+ - **Auto-disabled when absent** - an assistant whose CLI is not installed does not show a panel
24
+
25
+ ## Requirements
26
+
27
+ - JupyterLab >= 4.0.0
28
+ - Python >= 3.10
29
+
30
+ ## Install
31
+
32
+ To install the extension, execute:
33
+
34
+ ```bash
35
+ pip install jupyterlab_ai_code_assistants_extension
36
+ ```
37
+
38
+ ## Uninstall
39
+
40
+ To remove the extension, execute:
41
+
42
+ ```bash
43
+ pip uninstall jupyterlab_ai_code_assistants_extension
44
+ ```
@@ -0,0 +1,57 @@
1
+ import { ServerConnection } from '@jupyterlab/services';
2
+ import { ColourSource, ISession } from './types';
3
+ /** The colour vocabulary of `jupyterlab_colourful_tab_extension`, in its own
4
+ * order - that extension owns the tab CSS and these six ids; this module only
5
+ * feeds it one of them. The order is load-bearing: its localStorage records a
6
+ * user's menu choice as an INDEX into this list. */
7
+ export declare const TAB_COLOUR_IDS: readonly string[];
8
+ /** Map a conversation id onto one of the six colour ids. FNV-1a (32-bit) - a
9
+ * stable string hash with good avalanche on short hex-ish ids; `Math.imul`
10
+ * keeps the multiplication in 32-bit integer semantics. The same id always
11
+ * yields the same colour, on every reload and in every browser. */
12
+ export declare function fnv1aColour(sessionId: string): string;
13
+ /** Colour the user set by hand on a terminal tab, or null.
14
+ *
15
+ * The colourful-tab extension stores its right-click menu choices as
16
+ * `{ "terminal:<session name>": <index into TAB_COLOUR_IDS> }`. Reading it is
17
+ * what makes "set the colour on the tab" register as the conversation's
18
+ * colour for assistants whose CLI has no colour concept at all. */
19
+ export declare function readUserSetTabColour(terminalName: string): string | null;
20
+ /** Resolve the ladder for one conversation. `userSet` is this extension's own
21
+ * store, `native` the conversation's own colour (only ever populated by a
22
+ * `native` provider). */
23
+ export declare function effectiveColour(sessionId: string | null, source: ColourSource, userSet: string | null, native: string | null | undefined): string | null;
24
+ /** Fall back from an unreadable conversation to the project a terminal sits
25
+ * in: longest matching path wins, so a nested project beats its parent.
26
+ * Never the first resort - a row carries only its representative
27
+ * conversation, so resolving colour by row tints every terminal of a project
28
+ * with the representative's colour. */
29
+ export declare function sessionForCwds(cwds: string[], sessions: ISession[]): ISession | null;
30
+ /**
31
+ * Client for one provider's user-set colour store.
32
+ *
33
+ * The store is server-side and per provider (`providers/<id>/colours`), so
34
+ * colours survive a reload and one assistant's colours can never be read or
35
+ * written through another's routes. Reads are cached in memory and refreshed
36
+ * with the session poll; writes go straight through and update the cache.
37
+ */
38
+ export declare class ColourStore {
39
+ constructor(providerId: string, serverSettings: ServerConnection.ISettings);
40
+ /** User-set colour for a conversation, from the in-memory cache. */
41
+ get(sessionId: string | null | undefined): string | null;
42
+ /** Reload the whole store. Failures leave the previous cache in place - a
43
+ * momentarily unreachable server must not clear every user colour. */
44
+ load(): Promise<void>;
45
+ /** Record a colour as this conversation's own. Passing null drops the entry,
46
+ * which is also how a deleted conversation leaves no orphan key behind. */
47
+ set(sessionId: string, colour: string | null): Promise<void>;
48
+ /** Drop stored colours for conversations that no longer exist. */
49
+ forget(sessionIds: string[]): Promise<void>;
50
+ /** Copy the parent's EFFECTIVE colour onto a fresh branch, so a branch of a
51
+ * branch inherits whatever its immediate parent actually shows, user-set
52
+ * override included. Called at the fork sites, once the new id is known. */
53
+ inherit(parentColour: string | null, childId: string): Promise<void>;
54
+ private readonly _providerId;
55
+ private readonly _serverSettings;
56
+ private _colours;
57
+ }
@@ -0,0 +1,190 @@
1
+ // Terminal tab tint per conversation.
2
+ //
3
+ // Effective colour, highest wins:
4
+ // 1. user-set - the write-back store this module keeps, per provider
5
+ // 2. native - the conversation's own colour, when the assistant has one
6
+ // 3. derived - a stable hash of the conversation id
7
+ // 4. none - no tint
8
+ //
9
+ // Every step of that ladder is pure and exported on its own, because the tint
10
+ // is the part of this panel that regressed twice behind a green suite that
11
+ // never ran the logic.
12
+ import { requestProvider } from './request';
13
+ /** The colour vocabulary of `jupyterlab_colourful_tab_extension`, in its own
14
+ * order - that extension owns the tab CSS and these six ids; this module only
15
+ * feeds it one of them. The order is load-bearing: its localStorage records a
16
+ * user's menu choice as an INDEX into this list. */
17
+ export const TAB_COLOUR_IDS = [
18
+ 'rose',
19
+ 'peach',
20
+ 'lemon',
21
+ 'mint',
22
+ 'sky',
23
+ 'lavender'
24
+ ];
25
+ /** Key `jupyterlab_colourful_tab_extension` persists menu-set tab colours
26
+ * under, and the `terminal:<session name>` shape of its entries. Read only -
27
+ * this module never writes into another extension's storage. */
28
+ const TAB_COLOUR_STORAGE_KEY = 'jupyterlab-colourful-tab-colours';
29
+ /** Map a conversation id onto one of the six colour ids. FNV-1a (32-bit) - a
30
+ * stable string hash with good avalanche on short hex-ish ids; `Math.imul`
31
+ * keeps the multiplication in 32-bit integer semantics. The same id always
32
+ * yields the same colour, on every reload and in every browser. */
33
+ export function fnv1aColour(sessionId) {
34
+ let hash = 0x811c9dc5; // FNV offset basis
35
+ for (let i = 0; i < sessionId.length; i++) {
36
+ hash ^= sessionId.charCodeAt(i);
37
+ hash = Math.imul(hash, 0x01000193); // FNV prime
38
+ }
39
+ return TAB_COLOUR_IDS[(hash >>> 0) % TAB_COLOUR_IDS.length];
40
+ }
41
+ /** Colour the user set by hand on a terminal tab, or null.
42
+ *
43
+ * The colourful-tab extension stores its right-click menu choices as
44
+ * `{ "terminal:<session name>": <index into TAB_COLOUR_IDS> }`. Reading it is
45
+ * what makes "set the colour on the tab" register as the conversation's
46
+ * colour for assistants whose CLI has no colour concept at all. */
47
+ export function readUserSetTabColour(terminalName) {
48
+ var _a;
49
+ try {
50
+ const raw = window.localStorage.getItem(TAB_COLOUR_STORAGE_KEY);
51
+ if (!raw) {
52
+ return null;
53
+ }
54
+ const parsed = JSON.parse(raw);
55
+ const index = parsed[`terminal:${terminalName}`];
56
+ if (typeof index !== 'number') {
57
+ return null;
58
+ }
59
+ return (_a = TAB_COLOUR_IDS[index]) !== null && _a !== void 0 ? _a : null;
60
+ }
61
+ catch (_err) {
62
+ // localStorage unavailable, or another extension's schema changed under
63
+ // us - no user colour is a valid answer, a thrown error is not.
64
+ return null;
65
+ }
66
+ }
67
+ /** Resolve the ladder for one conversation. `userSet` is this extension's own
68
+ * store, `native` the conversation's own colour (only ever populated by a
69
+ * `native` provider). */
70
+ export function effectiveColour(sessionId, source, userSet, native) {
71
+ if (userSet) {
72
+ return userSet;
73
+ }
74
+ if (source === 'native') {
75
+ return native !== null && native !== void 0 ? native : null;
76
+ }
77
+ if (source === 'derived' && sessionId) {
78
+ return fnv1aColour(sessionId);
79
+ }
80
+ return null;
81
+ }
82
+ /** Fall back from an unreadable conversation to the project a terminal sits
83
+ * in: longest matching path wins, so a nested project beats its parent.
84
+ * Never the first resort - a row carries only its representative
85
+ * conversation, so resolving colour by row tints every terminal of a project
86
+ * with the representative's colour. */
87
+ export function sessionForCwds(cwds, sessions) {
88
+ let best = null;
89
+ let bestLen = -1;
90
+ for (const raw of cwds) {
91
+ const cwd = raw.replace(/\/+$/, '');
92
+ for (const s of sessions) {
93
+ const p = s.project_path.replace(/\/+$/, '');
94
+ if ((cwd === p || cwd.startsWith(p + '/')) && p.length > bestLen) {
95
+ best = s;
96
+ bestLen = p.length;
97
+ }
98
+ }
99
+ }
100
+ return best;
101
+ }
102
+ /**
103
+ * Client for one provider's user-set colour store.
104
+ *
105
+ * The store is server-side and per provider (`providers/<id>/colours`), so
106
+ * colours survive a reload and one assistant's colours can never be read or
107
+ * written through another's routes. Reads are cached in memory and refreshed
108
+ * with the session poll; writes go straight through and update the cache.
109
+ */
110
+ export class ColourStore {
111
+ constructor(providerId, serverSettings) {
112
+ this._colours = new Map();
113
+ this._providerId = providerId;
114
+ this._serverSettings = serverSettings;
115
+ }
116
+ /** User-set colour for a conversation, from the in-memory cache. */
117
+ get(sessionId) {
118
+ var _a;
119
+ if (!sessionId) {
120
+ return null;
121
+ }
122
+ return (_a = this._colours.get(sessionId)) !== null && _a !== void 0 ? _a : null;
123
+ }
124
+ /** Reload the whole store. Failures leave the previous cache in place - a
125
+ * momentarily unreachable server must not clear every user colour. */
126
+ async load() {
127
+ var _a;
128
+ try {
129
+ const data = await requestProvider(this._providerId, 'colours', this._serverSettings, { cache: 'no-store' });
130
+ const next = new Map();
131
+ for (const [id, colour] of Object.entries((_a = data.colours) !== null && _a !== void 0 ? _a : {})) {
132
+ if (typeof colour === 'string' && colour) {
133
+ next.set(id, colour);
134
+ }
135
+ }
136
+ this._colours = next;
137
+ }
138
+ catch (_err) {
139
+ // Keep the cache; the next poll retries.
140
+ }
141
+ }
142
+ /** Record a colour as this conversation's own. Passing null drops the entry,
143
+ * which is also how a deleted conversation leaves no orphan key behind. */
144
+ async set(sessionId, colour) {
145
+ if (!sessionId) {
146
+ return;
147
+ }
148
+ if (colour) {
149
+ this._colours.set(sessionId, colour);
150
+ }
151
+ else {
152
+ this._colours.delete(sessionId);
153
+ }
154
+ try {
155
+ await requestProvider(this._providerId, 'colours', this._serverSettings, {
156
+ method: 'POST',
157
+ body: JSON.stringify({ session_id: sessionId, colour })
158
+ });
159
+ }
160
+ catch (_err) {
161
+ // The cache already reflects the intent; the next load reconciles.
162
+ }
163
+ }
164
+ /** Drop stored colours for conversations that no longer exist. */
165
+ async forget(sessionIds) {
166
+ const present = sessionIds.filter(id => this._colours.has(id));
167
+ if (present.length === 0) {
168
+ return;
169
+ }
170
+ present.forEach(id => this._colours.delete(id));
171
+ try {
172
+ await requestProvider(this._providerId, 'colours', this._serverSettings, {
173
+ method: 'DELETE',
174
+ body: JSON.stringify({ session_ids: present })
175
+ });
176
+ }
177
+ catch (_err) {
178
+ // Same as `set`: the cache is authoritative until the next load.
179
+ }
180
+ }
181
+ /** Copy the parent's EFFECTIVE colour onto a fresh branch, so a branch of a
182
+ * branch inherits whatever its immediate parent actually shows, user-set
183
+ * override included. Called at the fork sites, once the new id is known. */
184
+ async inherit(parentColour, childId) {
185
+ if (!parentColour || !childId) {
186
+ return;
187
+ }
188
+ await this.set(childId, parentColour);
189
+ }
190
+ }
@@ -0,0 +1,12 @@
1
+ import { LabIcon } from '@jupyterlab/ui-components';
2
+ export declare const assistantsIcon: LabIcon;
3
+ export declare const starFilledIcon: LabIcon;
4
+ export declare const refreshIcon: LabIcon;
5
+ export declare const removeIcon: LabIcon;
6
+ export declare const warningIcon: LabIcon;
7
+ export declare const addIcon: LabIcon;
8
+ export declare const branchIcon: LabIcon;
9
+ export declare const switchIcon: LabIcon;
10
+ export declare const filterIcon: LabIcon;
11
+ /** Build (or reuse) the LabIcon for a provider's own glyph. */
12
+ export declare function providerIcon(name: string, svgstr: string): LabIcon;
@@ -0,0 +1,99 @@
1
+ import { LabIcon } from '@jupyterlab/ui-components';
2
+ // Shared panel chrome icons. Provider icons are NOT here - each provider
3
+ // carries its own SVG on its descriptor and the registry builds the LabIcon
4
+ // from it, so adding an assistant touches no core file.
5
+ //
6
+ // Material-style glyphs matched to the rest of the sidebar so header and menu
7
+ // icons render at identical size and theme.
8
+ const ICON_PREFIX = 'jupyterlab_ai_code_assistants_extension';
9
+ // The extension's own identity, used by the settings entry - one section for
10
+ // all assistants, so it cannot borrow any one assistant's glyph. Material
11
+ // "smart_toy" outline.
12
+ const assistantsSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
13
+ <path class="jp-icon3" fill="#616161" d="M20 9V7c0-1.1-.9-2-2-2h-3c0-1.66-1.34-3-3-3S9 3.34 9 5H6c-1.1 0-2 .9-2 2v2c-1.66 0-3 1.34-3 3s1.34 3 3 3v4c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-4c1.66 0 3-1.34 3-3s-1.34-3-3-3zm-2 10H6V7h12v12zM9 13.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm7.5-1.5c0 .83-.67 1.5-1.5 1.5s-1.5-.67-1.5-1.5.67-1.5 1.5-1.5 1.5.67 1.5 1.5zM8 15h8v2H8v-2z"/>
14
+ </svg>`;
15
+ export const assistantsIcon = new LabIcon({
16
+ name: `${ICON_PREFIX}:assistants`,
17
+ svgstr: assistantsSvgStr
18
+ });
19
+ const starFilledSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
20
+ <path class="jp-icon3" fill="#616161" d="M12 2.5l2.9 6.4 7 .7-5.3 4.7 1.6 6.9L12 17.7l-6.2 3.5 1.6-6.9-5.3-4.7 7-.7z"/>
21
+ </svg>`;
22
+ export const starFilledIcon = new LabIcon({
23
+ name: `${ICON_PREFIX}:star-filled`,
24
+ svgstr: starFilledSvgStr
25
+ });
26
+ const refreshSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
27
+ <path class="jp-icon3" fill="#616161" d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
28
+ </svg>`;
29
+ export const refreshIcon = new LabIcon({
30
+ name: `${ICON_PREFIX}:refresh`,
31
+ svgstr: refreshSvgStr
32
+ });
33
+ const removeSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
34
+ <path class="jp-icon3" fill="#616161" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM8 9h8v10H8V9zm7.5-5l-1-1h-5l-1 1H5v2h14V4h-3.5z"/>
35
+ </svg>`;
36
+ export const removeIcon = new LabIcon({
37
+ name: `${ICON_PREFIX}:remove`,
38
+ svgstr: removeSvgStr
39
+ });
40
+ // Marks the unsafe launch variants. A shield reads as "protected", which is
41
+ // the inverse of what these entries do, so the caution triangle carries them
42
+ // instead. Material "warning" outline.
43
+ const warningSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="13" height="13">
44
+ <path class="jp-icon3" fill="#616161" d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
45
+ </svg>`;
46
+ export const warningIcon = new LabIcon({
47
+ name: `${ICON_PREFIX}:warning`,
48
+ svgstr: warningSvgStr
49
+ });
50
+ const addSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
51
+ <path class="jp-icon3" fill="#616161" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
52
+ </svg>`;
53
+ export const addIcon = new LabIcon({
54
+ name: `${ICON_PREFIX}:add`,
55
+ svgstr: addSvgStr
56
+ });
57
+ // Git-branch glyph (Octicons git-branch-16, MIT) - marks rows carrying
58
+ // parallel conversations and the Branch Session entries.
59
+ const branchSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
60
+ <path class="jp-icon3" fill="#616161" d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628a2.25 2.25 0 0 1-1.5-2.122Zm-6 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"/>
61
+ </svg>`;
62
+ export const branchIcon = new LabIcon({
63
+ name: `${ICON_PREFIX}:branch`,
64
+ svgstr: branchSvgStr
65
+ });
66
+ // Arrow-switch glyph (Octicons arrow-switch-16, MIT) - the switch-and-manage
67
+ // submenu.
68
+ const switchSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
69
+ <path class="jp-icon3" fill="#616161" d="M5.22 14.78a.75.75 0 0 0 1.06-1.06L4.56 12h8.69a.75.75 0 0 0 0-1.5H4.56l1.72-1.72a.75.75 0 0 0-1.06-1.06l-3 3a.75.75 0 0 0 0 1.06l3 3Zm5.56-6.5a.75.75 0 1 1-1.06-1.06l1.72-1.72H2.75a.75.75 0 0 1 0-1.5h8.69L9.72 2.28a.75.75 0 0 1 1.06-1.06l3 3a.75.75 0 0 1 0 1.06l-3 3Z"/>
70
+ </svg>`;
71
+ export const switchIcon = new LabIcon({
72
+ name: `${ICON_PREFIX}:switch`,
73
+ svgstr: switchSvgStr
74
+ });
75
+ // Funnel copied verbatim from @jupyterlab/ui-components' `search/filter.svg` -
76
+ // the same image the file browser's filter toggle uses. `class="jp-icon3"`
77
+ // lets the theme drive the fill, so the source `fill="#FFF"` is inert under
78
+ // the standard light/dark themes.
79
+ const filterSvgStr = `<svg xmlns="http://www.w3.org/2000/svg" width="16" viewBox="0 0 24 24">
80
+ <path fill="#FFF" d="M14 12v7.88c.04.3-.06.62-.29.83a.996.996 0 0 1-1.41 0l-2.01-2.01a.99.99 0 0 1-.29-.83V12h-.03L4.21 4.62a1 1 0 0 1 .17-1.4c.19-.14.4-.22.62-.22h14c.22 0 .43.08.62.22a1 1 0 0 1 .17 1.4L14.03 12z" class="jp-icon3"/>
81
+ </svg>`;
82
+ export const filterIcon = new LabIcon({
83
+ name: `${ICON_PREFIX}:filter`,
84
+ svgstr: filterSvgStr
85
+ });
86
+ // Provider glyphs already built, by icon name. LabIcon warns and returns the
87
+ // first registration for a duplicate name, so a provider re-registered by a
88
+ // live settings toggle must resolve to the icon it built the first time
89
+ // rather than construct a second one on every toggle.
90
+ const _providerIcons = new Map();
91
+ /** Build (or reuse) the LabIcon for a provider's own glyph. */
92
+ export function providerIcon(name, svgstr) {
93
+ let icon = _providerIcons.get(name);
94
+ if (!icon) {
95
+ icon = new LabIcon({ name, svgstr });
96
+ _providerIcons.set(name, icon);
97
+ }
98
+ return icon;
99
+ }
@@ -0,0 +1,13 @@
1
+ import { ILaunchMode } from './types';
2
+ /**
3
+ * The single launch-mode token for one action, or undefined for a plain launch.
4
+ *
5
+ * The payload carries at most one mode, because the server maps one token to
6
+ * one flag. `force` wins outright - that is what the menu's variant entries
7
+ * do. Otherwise an enum mode moved off its default takes precedence over any
8
+ * boolean switch: the enum is the explicit statement of intent, so a boolean
9
+ * left on must not silently invert it. A boolean fires only while every enum
10
+ * mode sits at its default. An enum at its default sends nothing - it is what
11
+ * the CLI would do unasked.
12
+ */
13
+ export declare function resolveLaunchMode(launchModes: ILaunchMode[], values: Record<string, boolean | string>, force?: string): string | undefined;
@@ -0,0 +1,36 @@
1
+ // Launch-mode resolution: which single mode token, if any, one launch carries.
2
+ //
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.
6
+ /**
7
+ * The single launch-mode token for one action, or undefined for a plain launch.
8
+ *
9
+ * The payload carries at most one mode, because the server maps one token to
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.
16
+ */
17
+ export function resolveLaunchMode(launchModes, values, force) {
18
+ if (force) {
19
+ return force;
20
+ }
21
+ 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) {
32
+ return mode.id;
33
+ }
34
+ }
35
+ return undefined;
36
+ }