jupyterlab_ai_code_assistants_extension 1.2.27 → 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 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
@@ -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');
@@ -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
@@ -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
  {
@@ -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: [],
@@ -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: [
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab_ai_code_assistants_extension",
3
- "version": "1.2.27",
3
+ "version": "1.2.30",
4
4
  "description": "A full launcher and manager for every AI code assistant in JupyterLab - Claude Code, Codex, Kimi, Gemini and DeepSeek. Start, resume, fork, switch, and clean up CLI sessions from a side panel per assistant - one click lands you in the right terminal with the assistant already running, no duplicate tabs, no UUID hunting.",
5
5
  "keywords": [
6
6
  "jupyter",