jupyterlab_ai_code_assistants_extension 1.2.25 → 1.2.30
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 +2 -1
- package/lib/core/panel.d.ts +15 -0
- package/lib/core/panel.js +83 -1
- package/lib/core/popup.js +1 -1
- package/lib/core/types.d.ts +11 -0
- package/lib/providers/claude.js +3 -0
- package/lib/providers/codex.js +3 -0
- package/lib/providers/deepseek.js +2 -0
- package/lib/providers/gemini.js +2 -0
- package/lib/providers/kimi.js +2 -0
- package/package.json +2 -2
- package/schema/plugin.json +1 -1
- package/src/__tests__/index.spec.ts +746 -1
- package/src/__tests__/launcher.spec.ts +1 -0
- package/src/__tests__/panel.spec.ts +719 -2
- package/src/__tests__/registry.spec.ts +2 -0
- package/src/__tests__/tab-colour-capture.spec.ts +1 -0
- package/src/__tests__/terminals.spec.ts +1 -0
- package/src/core/panel.ts +89 -0
- package/src/core/popup.ts +1 -1
- package/src/core/types.ts +12 -0
- package/src/providers/claude.ts +3 -0
- package/src/providers/codex.ts +3 -0
- package/src/providers/deepseek.ts +2 -0
- package/src/providers/gemini.ts +2 -0
- package/src/providers/kimi.ts +2 -0
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ Chat-panel extensions re-implement the agent loop and trail the real tool. This
|
|
|
34
34
|
- **Your terminal's `claude -c` follows the panel** (Claude) - switching to a conversation makes plain `claude -c` in that project resume that same conversation, including one that has been compacted, which Claude Code otherwise refuses to continue. Launching repairs a compacted conversation the same way, but only switching moves what `claude -c` picks
|
|
35
35
|
- **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
|
|
36
36
|
- **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, Gemini and DeepSeek) behind the same menu item
|
|
37
|
+
- **Rename session** - name a conversation from the right-click menu, and the name is written where the assistant itself reads it back: Claude gets the record its own `/rename` writes, Kimi a custom title, Gemini the chat's summary, DeepSeek its title event. The panel shows the name the assistant actually kept. Codex keeps its names in a database this extension only reads, and its CLI has no rename verb, so the item is absent there rather than failing
|
|
37
38
|
- **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
|
|
38
39
|
- **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, Gemini and DeepSeek 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. Tinting needs the companion release that reports the colours you pick and lets another extension own a tab - against an older one this extension tints no tabs at all and says so once, and the companion's own right-click colours keep working as they always did
|
|
39
40
|
- **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. Clearing the colour on the tab itself releases it too, for the conversation that terminal is running; `Reset Tab Colour` is the way back for a conversation with no tab open. A Codex or Kimi conversation that has never been resumed cannot be tracked yet, so a colour set on its tab is not kept
|
|
@@ -49,7 +50,7 @@ Chat-panel extensions re-implement the agent loop and trail the real tool. This
|
|
|
49
50
|
|
|
50
51
|
## Requirements
|
|
51
52
|
|
|
52
|
-
- JupyterLab >= 4.
|
|
53
|
+
- JupyterLab >= 4.6.0
|
|
53
54
|
- Python >= 3.10
|
|
54
55
|
- At least one assistant CLI on `PATH`: `claude`, `codex`, `kimi`, `gemini`, or `dsh` (the DeepSeek Harness)
|
|
55
56
|
|
package/lib/core/panel.d.ts
CHANGED
|
@@ -181,6 +181,21 @@ export declare class AssistantSessionsPanel extends Widget {
|
|
|
181
181
|
* alone would not - the actively written parent overtakes it) and inherits
|
|
182
182
|
* the parent's tint. */
|
|
183
183
|
private _watchForNewBranch;
|
|
184
|
+
/** Name the row's current conversation, in the assistant's own store.
|
|
185
|
+
*
|
|
186
|
+
* Only ever offered where the descriptor says the assistant keeps a name
|
|
187
|
+
* this extension can write - see `canRename`. The field is seeded from the
|
|
188
|
+
* conversation's OWN name and left empty otherwise: the row falls back to
|
|
189
|
+
* the project folder when a conversation is unnamed, and seeding that would
|
|
190
|
+
* have the user accept a dialog that silently names the conversation after
|
|
191
|
+
* its directory.
|
|
192
|
+
*/
|
|
193
|
+
private _renameSession;
|
|
194
|
+
/** What to tell the user about a refused rename. `rename_failed` is the one
|
|
195
|
+
* refusal a correct request can still earn - an assistant that keeps names
|
|
196
|
+
* per conversation may have none to rewrite yet - so it gets words rather
|
|
197
|
+
* than the raw code. */
|
|
198
|
+
private _renameError;
|
|
184
199
|
/** Make another conversation the row's current one. */
|
|
185
200
|
private _switchBranch;
|
|
186
201
|
/** Delete conversations of the active row's project. Resolves with the
|
package/lib/core/panel.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Clipboard, Dialog, InputDialog, Notification, showDialog } from '@jupyterlab/apputils';
|
|
2
|
-
import { MenuSvg, closeIcon, folderIcon, terminalIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
import { MenuSvg, closeIcon, editIcon, folderIcon, terminalIcon } from '@jupyterlab/ui-components';
|
|
3
3
|
import { nullTranslator } from '@jupyterlab/translation';
|
|
4
4
|
import { CommandRegistry } from '@lumino/commands';
|
|
5
5
|
import { UUID } from '@lumino/coreutils';
|
|
@@ -1199,6 +1199,73 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
1199
1199
|
};
|
|
1200
1200
|
window.setTimeout(() => void tick(), BRANCH_WATCH_INTERVAL_MS);
|
|
1201
1201
|
}
|
|
1202
|
+
/** Name the row's current conversation, in the assistant's own store.
|
|
1203
|
+
*
|
|
1204
|
+
* Only ever offered where the descriptor says the assistant keeps a name
|
|
1205
|
+
* this extension can write - see `canRename`. The field is seeded from the
|
|
1206
|
+
* conversation's OWN name and left empty otherwise: the row falls back to
|
|
1207
|
+
* the project folder when a conversation is unnamed, and seeding that would
|
|
1208
|
+
* have the user accept a dialog that silently names the conversation after
|
|
1209
|
+
* its directory.
|
|
1210
|
+
*/
|
|
1211
|
+
async _renameSession(session) {
|
|
1212
|
+
var _a;
|
|
1213
|
+
const named = await InputDialog.getText({
|
|
1214
|
+
title: 'Rename Session',
|
|
1215
|
+
label: `Name for this ${this._descriptor.label} conversation`,
|
|
1216
|
+
text: session.name_source === 'session' ? session.name : '',
|
|
1217
|
+
placeholder: this._lookupName(session)
|
|
1218
|
+
});
|
|
1219
|
+
if (!named.button.accept) {
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
const name = ((_a = named.value) !== null && _a !== void 0 ? _a : '').trim();
|
|
1223
|
+
if (!name) {
|
|
1224
|
+
// Refused the way Branch Session refuses an empty name (DEF-43): the
|
|
1225
|
+
// placeholder shows the current name, which reads as "press Ok for the
|
|
1226
|
+
// default", so an empty Ok has to say that nothing happened.
|
|
1227
|
+
Notification.warning('Enter a name - nothing was renamed.', {
|
|
1228
|
+
autoClose: 4000
|
|
1229
|
+
});
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
try {
|
|
1233
|
+
const result = await requestProvider(this._descriptor.id, 'rename', this._serverSettings, {
|
|
1234
|
+
method: 'POST',
|
|
1235
|
+
body: JSON.stringify({
|
|
1236
|
+
encoded_path: session.encoded_path,
|
|
1237
|
+
session_id: session.session_id,
|
|
1238
|
+
name
|
|
1239
|
+
})
|
|
1240
|
+
});
|
|
1241
|
+
// The stored name, never the typed one - a store may normalise what it
|
|
1242
|
+
// writes, and echoing the request would report a name that is not on
|
|
1243
|
+
// disk.
|
|
1244
|
+
Notification.success(`Renamed to ${result.name}`, { autoClose: 3000 });
|
|
1245
|
+
}
|
|
1246
|
+
catch (err) {
|
|
1247
|
+
Notification.error(this._renameError(err), { autoClose: 4000 });
|
|
1248
|
+
}
|
|
1249
|
+
finally {
|
|
1250
|
+
// The switcher submenu labels branches from this cache, so a stale one
|
|
1251
|
+
// would keep showing the old name next to the new row title.
|
|
1252
|
+
this._branchCache = null;
|
|
1253
|
+
await this._fetch().catch(() => undefined);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
/** What to tell the user about a refused rename. `rename_failed` is the one
|
|
1257
|
+
* refusal a correct request can still earn - an assistant that keeps names
|
|
1258
|
+
* per conversation may have none to rewrite yet - so it gets words rather
|
|
1259
|
+
* than the raw code. */
|
|
1260
|
+
_renameError(err) {
|
|
1261
|
+
if (isResponseStatus(err, 404)) {
|
|
1262
|
+
return 'That conversation no longer exists - the list has been refreshed.';
|
|
1263
|
+
}
|
|
1264
|
+
if ((err === null || err === void 0 ? void 0 : err.message) === 'rename_failed') {
|
|
1265
|
+
return `${this._descriptor.label} would not store a name for this conversation.`;
|
|
1266
|
+
}
|
|
1267
|
+
return `Rename failed: ${err}`;
|
|
1268
|
+
}
|
|
1202
1269
|
/** Make another conversation the row's current one. */
|
|
1203
1270
|
async _switchBranch(session, sessionId) {
|
|
1204
1271
|
var _a;
|
|
@@ -2104,6 +2171,20 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2104
2171
|
},
|
|
2105
2172
|
execute: () => void this._branchSession()
|
|
2106
2173
|
});
|
|
2174
|
+
this._commands.addCommand(this._cmd('rename-session'), {
|
|
2175
|
+
label: this._trans.__('Rename Session...'),
|
|
2176
|
+
icon: editIcon,
|
|
2177
|
+
// Absent, not disabled, for an assistant with no writable name: a
|
|
2178
|
+
// greyed item invites the user to find out why, and the answer is that
|
|
2179
|
+
// this assistant will never have one.
|
|
2180
|
+
isVisible: () => this._descriptor.canRename,
|
|
2181
|
+
execute: () => {
|
|
2182
|
+
const s = active();
|
|
2183
|
+
if (s) {
|
|
2184
|
+
void this._renameSession(s);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
});
|
|
2107
2188
|
this._commands.addCommand(this._cmd('remove'), {
|
|
2108
2189
|
label: `Remove from ${this._descriptor.label}`,
|
|
2109
2190
|
icon: removeIcon,
|
|
@@ -2197,6 +2278,7 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2197
2278
|
add('open-terminal');
|
|
2198
2279
|
add('show-in-filebrowser');
|
|
2199
2280
|
add('toggle-favourite');
|
|
2281
|
+
add('rename-session');
|
|
2200
2282
|
add('copy-path');
|
|
2201
2283
|
add('copy-session-id');
|
|
2202
2284
|
add('reset-colour');
|
package/lib/core/popup.js
CHANGED
|
@@ -291,7 +291,7 @@ export function showManageSessionsPopup(options) {
|
|
|
291
291
|
});
|
|
292
292
|
// Busy-lock the whole body during the async delete, so a slow backend cannot
|
|
293
293
|
// be double-clicked into deleting the same set twice and a mid-flight
|
|
294
|
-
// selection cannot be silently discarded. The Dialog's
|
|
294
|
+
// selection cannot be silently discarded. The Dialog's Close button sits
|
|
295
295
|
// outside the body and stays usable.
|
|
296
296
|
const setDeleting = (on) => {
|
|
297
297
|
deleting = on;
|
package/lib/core/types.d.ts
CHANGED
|
@@ -69,6 +69,11 @@ export interface IProviderDescriptor {
|
|
|
69
69
|
/** Whether the branch flow asks the user to name the new conversation.
|
|
70
70
|
* Where that name then GOES is `forkStrategy`'s job. */
|
|
71
71
|
promptsForBranchName: boolean;
|
|
72
|
+
/** The assistant keeps a conversation name this extension can write where
|
|
73
|
+
* the assistant itself reads it back. False hides the rename action rather
|
|
74
|
+
* than offering one that cannot land - which is the honest answer for an
|
|
75
|
+
* assistant whose names live in a store this extension only reads. */
|
|
76
|
+
canRename: boolean;
|
|
72
77
|
/** The CLI starts a brand-new conversation under an id we give it, so a
|
|
73
78
|
* fresh session is identifiable from its argv on the first poll instead of
|
|
74
79
|
* being an unknown terminal until it writes its store entry. Independent of
|
|
@@ -193,6 +198,12 @@ export interface ISwitchResponse {
|
|
|
193
198
|
export interface IForkResponse {
|
|
194
199
|
session_id: string;
|
|
195
200
|
}
|
|
201
|
+
export interface IRenameResponse {
|
|
202
|
+
session_id: string;
|
|
203
|
+
/** The name as STORED, which a provider may have normalised - never assume
|
|
204
|
+
* it is the string that was sent. */
|
|
205
|
+
name: string;
|
|
206
|
+
}
|
|
196
207
|
export interface ILaunchRequest {
|
|
197
208
|
project_path: string;
|
|
198
209
|
/** The project's store key. Carrying it arms the server's launch-time pin
|
package/lib/providers/claude.js
CHANGED
|
@@ -47,6 +47,9 @@ export const descriptor = {
|
|
|
47
47
|
// every turn, which is the only way a fork's name survives - a title written
|
|
48
48
|
// after the fact loses to the parent title the fork inherits.
|
|
49
49
|
promptsForBranchName: true,
|
|
50
|
+
// `/rename` writes a `custom-title` record into the transcript, and the
|
|
51
|
+
// store writes the same record.
|
|
52
|
+
canRename: true,
|
|
50
53
|
mintsNewSessionId: true,
|
|
51
54
|
launchModes: [
|
|
52
55
|
{
|
package/lib/providers/codex.js
CHANGED
|
@@ -42,6 +42,9 @@ export const descriptor = {
|
|
|
42
42
|
// Forks are unnamed - Codex has no naming flag and no writable title field,
|
|
43
43
|
// so the panel never asks for a name it could not stamp anywhere.
|
|
44
44
|
promptsForBranchName: false,
|
|
45
|
+
// No writable name: a thread's name lives in the sqlite index the CLI
|
|
46
|
+
// owns, and Codex has no rename subcommand to shell to.
|
|
47
|
+
canRename: false,
|
|
45
48
|
// A bare `codex` mints its own thread id; there is no `--session-id`.
|
|
46
49
|
mintsNewSessionId: false,
|
|
47
50
|
launchModes: [
|
|
@@ -48,6 +48,8 @@ export const descriptor = {
|
|
|
48
48
|
colourSource: 'none',
|
|
49
49
|
terminalScope: 'project',
|
|
50
50
|
promptsForBranchName: true,
|
|
51
|
+
// The name is the newest `session/title` event of the log.
|
|
52
|
+
canRename: true,
|
|
51
53
|
mintsNewSessionId: false,
|
|
52
54
|
sessionIdPrefix: 'session-',
|
|
53
55
|
launchModes: [],
|
package/lib/providers/gemini.js
CHANGED
|
@@ -47,6 +47,8 @@ export const descriptor = {
|
|
|
47
47
|
// The fork's name is written into the copy by the server; the CLI has no
|
|
48
48
|
// naming flag to take it.
|
|
49
49
|
promptsForBranchName: true,
|
|
50
|
+
// The name is `summary` in the chat file, which is what the CLI shows.
|
|
51
|
+
canRename: true,
|
|
50
52
|
// `--session-id <uuid>` starts a conversation under an id we mint.
|
|
51
53
|
mintsNewSessionId: true,
|
|
52
54
|
launchModes: [
|
package/lib/providers/kimi.js
CHANGED
|
@@ -44,6 +44,8 @@ export const descriptor = {
|
|
|
44
44
|
// The fork's name is written into the copy's `state.json` by the server; the
|
|
45
45
|
// CLI has no naming flag to take it.
|
|
46
46
|
promptsForBranchName: true,
|
|
47
|
+
// The name is `title` in the conversation's `state.json`.
|
|
48
|
+
canRename: true,
|
|
47
49
|
// A bare `kimi` mints its own id and writes it on the first turn, and `-S`
|
|
48
50
|
// only ever resumes - so a new conversation cannot be launched under an id
|
|
49
51
|
// we chose, and is identified from the store on the next poll instead.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab_ai_code_assistants_extension",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.2.30",
|
|
4
|
+
"description": "A full launcher and manager for every AI code assistant in JupyterLab - Claude Code, Codex, Kimi, Gemini and DeepSeek. Start, resume, fork, switch, and clean up CLI sessions from a side panel per assistant - one click lands you in the right terminal with the assistant already running, no duplicate tabs, no UUID hunting.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
7
7
|
"jupyterlab",
|
package/schema/plugin.json
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"colouredTabs": {
|
|
24
24
|
"type": "boolean",
|
|
25
25
|
"title": "Coloured terminal tabs",
|
|
26
|
-
"description": "When enabled (default), a terminal running an assistant has its dock tab tinted with that conversation's colour. Turning it off clears the tint and stops it being applied. Requires `jupyterlab_colourful_tab_extension
|
|
26
|
+
"description": "When enabled (default), a terminal running an assistant has its dock tab tinted with that conversation's colour. Turning it off clears the tint and stops it being applied. Requires `jupyterlab_colourful_tab_extension`, installed with this extension as a dependency.",
|
|
27
27
|
"default": true
|
|
28
28
|
},
|
|
29
29
|
"sidebar": {
|