jupyterlab_claude_code_extension 1.0.16 → 1.0.19
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/lib/index.js +4 -0
- package/lib/types.d.ts +7 -0
- package/lib/widget.d.ts +7 -1
- package/lib/widget.js +71 -41
- package/package.json +1 -2
- package/src/index.ts +4 -0
- package/src/types.ts +9 -0
- package/src/widget.ts +81 -47
- package/style/base.css +23 -0
package/lib/index.js
CHANGED
|
@@ -34,6 +34,10 @@ const plugin = {
|
|
|
34
34
|
const resolve = settings.get('resolveSessionNames')
|
|
35
35
|
.composite;
|
|
36
36
|
widget.setResolveSessionNames(resolve !== false);
|
|
37
|
+
const limit = settings.get('recentLimit').composite;
|
|
38
|
+
if (typeof limit === 'number') {
|
|
39
|
+
widget.setRecentLimit(limit);
|
|
40
|
+
}
|
|
37
41
|
};
|
|
38
42
|
apply();
|
|
39
43
|
settings.changed.connect(apply);
|
package/lib/types.d.ts
CHANGED
|
@@ -34,3 +34,10 @@ export interface IRemoveRequest {
|
|
|
34
34
|
export interface IRemoveResponse {
|
|
35
35
|
removed: string;
|
|
36
36
|
}
|
|
37
|
+
export interface ILaunchTerminalRequest {
|
|
38
|
+
project_path: string;
|
|
39
|
+
session_id: string;
|
|
40
|
+
}
|
|
41
|
+
export interface ILaunchTerminalResponse {
|
|
42
|
+
terminal_name: string;
|
|
43
|
+
}
|
package/lib/widget.d.ts
CHANGED
|
@@ -7,10 +7,15 @@ export declare class ClaudeCodeSessionsWidget extends Widget {
|
|
|
7
7
|
refresh(): void;
|
|
8
8
|
/** Toggle whether explicit ``/rename`` names are honoured. */
|
|
9
9
|
setResolveSessionNames(on: boolean): void;
|
|
10
|
+
/** Set how many rows the Recent section displays. */
|
|
11
|
+
setRecentLimit(n: number): void;
|
|
10
12
|
protected onAfterShow(_msg: Message): void;
|
|
11
13
|
protected onBeforeHide(_msg: Message): void;
|
|
12
14
|
protected onCloseRequest(msg: Message): void;
|
|
13
15
|
private _buildShell;
|
|
16
|
+
/** Lowercase substring + subsequence match. */
|
|
17
|
+
private _fuzzyMatch;
|
|
18
|
+
private _matchesFilter;
|
|
14
19
|
private _showLoading;
|
|
15
20
|
private _showError;
|
|
16
21
|
private _fetch;
|
|
@@ -18,7 +23,6 @@ export declare class ClaudeCodeSessionsWidget extends Widget {
|
|
|
18
23
|
private _remove;
|
|
19
24
|
private _resumeInTerminal;
|
|
20
25
|
private _doResumeInTerminal;
|
|
21
|
-
private _shellQuote;
|
|
22
26
|
private _findTerminalForCwd;
|
|
23
27
|
private _wireTerminalDisposal;
|
|
24
28
|
/** Apply the resolve-names setting + path-segment disambiguation. */
|
|
@@ -56,5 +60,7 @@ export declare class ClaudeCodeSessionsWidget extends Widget {
|
|
|
56
60
|
private readonly _pendingByPath;
|
|
57
61
|
private readonly _rootDir;
|
|
58
62
|
private _resolveNames;
|
|
63
|
+
private _recentLimit;
|
|
59
64
|
private _displayNames;
|
|
65
|
+
private _filter;
|
|
60
66
|
}
|
package/lib/widget.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Menu, Widget } from '@lumino/widgets';
|
|
|
3
3
|
import { requestAPI } from './request';
|
|
4
4
|
import { claudeIcon, refreshIcon, removeIcon, starFilledIcon } from './icons';
|
|
5
5
|
const POLL_INTERVAL_MS = 10000;
|
|
6
|
-
const
|
|
6
|
+
const DEFAULT_RECENT_LIMIT = 10;
|
|
7
7
|
const EXPANDED_STORAGE_KEY = 'jupyterlab_claude_code_extension:expanded';
|
|
8
8
|
const SECTION_LABELS = {
|
|
9
9
|
favourites: 'Favourites',
|
|
@@ -65,7 +65,9 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
65
65
|
this._terminalsByPath = new Map();
|
|
66
66
|
this._pendingByPath = new Map();
|
|
67
67
|
this._resolveNames = true;
|
|
68
|
+
this._recentLimit = DEFAULT_RECENT_LIMIT;
|
|
68
69
|
this._displayNames = new Map();
|
|
70
|
+
this._filter = '';
|
|
69
71
|
this._app = app;
|
|
70
72
|
this._serverSettings = app.serviceManager.serverSettings;
|
|
71
73
|
this._rootDir = rootDir.replace(/\/+$/, '');
|
|
@@ -92,6 +94,15 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
92
94
|
this._resolveNames = on;
|
|
93
95
|
this._render();
|
|
94
96
|
}
|
|
97
|
+
/** Set how many rows the Recent section displays. */
|
|
98
|
+
setRecentLimit(n) {
|
|
99
|
+
const clamped = Math.max(1, Math.min(100, Math.floor(n)));
|
|
100
|
+
if (this._recentLimit === clamped) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this._recentLimit = clamped;
|
|
104
|
+
this._render();
|
|
105
|
+
}
|
|
95
106
|
onAfterShow(_msg) {
|
|
96
107
|
this.refresh();
|
|
97
108
|
this._startPolling();
|
|
@@ -120,16 +131,53 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
120
131
|
refreshBtn.addEventListener('click', () => this.refresh());
|
|
121
132
|
header.appendChild(refreshBtn);
|
|
122
133
|
this._refreshBtn = refreshBtn;
|
|
134
|
+
const search = document.createElement('input');
|
|
135
|
+
search.type = 'search';
|
|
136
|
+
search.className = 'jp-ClaudeSessionsPanel-search';
|
|
137
|
+
search.placeholder = 'Filter sessions...';
|
|
138
|
+
search.spellcheck = false;
|
|
139
|
+
search.addEventListener('input', () => {
|
|
140
|
+
this._filter = search.value;
|
|
141
|
+
this._render();
|
|
142
|
+
});
|
|
123
143
|
const body = document.createElement('div');
|
|
124
144
|
body.className = 'jp-ClaudeSessionsPanel-body';
|
|
125
145
|
const status = document.createElement('div');
|
|
126
146
|
status.className = 'jp-ClaudeSessionsPanel-status';
|
|
127
147
|
root.appendChild(header);
|
|
148
|
+
root.appendChild(search);
|
|
128
149
|
root.appendChild(body);
|
|
129
150
|
root.appendChild(status);
|
|
130
151
|
this._bodyEl = body;
|
|
131
152
|
this._statusEl = status;
|
|
132
153
|
}
|
|
154
|
+
/** Lowercase substring + subsequence match. */
|
|
155
|
+
_fuzzyMatch(haystack, needle) {
|
|
156
|
+
if (!needle) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
const h = haystack.toLowerCase();
|
|
160
|
+
const n = needle.toLowerCase();
|
|
161
|
+
if (h.includes(n)) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
let j = 0;
|
|
165
|
+
for (let i = 0; i < h.length && j < n.length; i++) {
|
|
166
|
+
if (h[i] === n[j]) {
|
|
167
|
+
j += 1;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return j === n.length;
|
|
171
|
+
}
|
|
172
|
+
_matchesFilter(s) {
|
|
173
|
+
const q = this._filter.trim();
|
|
174
|
+
if (!q) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
return (this._fuzzyMatch(s.name, q) ||
|
|
178
|
+
this._fuzzyMatch(s.project_path, q) ||
|
|
179
|
+
this._fuzzyMatch(this._lookupName(s), q));
|
|
180
|
+
}
|
|
133
181
|
_showLoading() {
|
|
134
182
|
this._statusEl.textContent = this._sessions === null ? 'Loading...' : '';
|
|
135
183
|
}
|
|
@@ -201,7 +249,6 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
201
249
|
return promise;
|
|
202
250
|
}
|
|
203
251
|
async _doResumeInTerminal(session) {
|
|
204
|
-
var _a, _b, _c;
|
|
205
252
|
try {
|
|
206
253
|
// 1. In-memory microcache - covers rapid-click and post-creation reuse
|
|
207
254
|
// before the new widget propagates fully through the tracker. Cleared
|
|
@@ -221,49 +268,30 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
221
268
|
this._app.shell.activateById(found.id);
|
|
222
269
|
return;
|
|
223
270
|
}
|
|
224
|
-
// 3. No matching
|
|
225
|
-
|
|
271
|
+
// 3. No matching terminal - spawn a new one with `claude --resume <id>`
|
|
272
|
+
// as the pty's only process (no shell). Server-side endpoint calls
|
|
273
|
+
// terminal_manager.create(shell_command=[claude, --resume, sid], cwd=...)
|
|
274
|
+
// and returns the terminal name; we then attach JL's standard widget
|
|
275
|
+
// via terminal:open. When claude exits, the tab closes.
|
|
276
|
+
const launched = await requestAPI('launch-terminal', this._serverSettings, {
|
|
277
|
+
method: 'POST',
|
|
278
|
+
body: JSON.stringify({
|
|
279
|
+
project_path: session.project_path,
|
|
280
|
+
session_id: session.session_id
|
|
281
|
+
})
|
|
282
|
+
});
|
|
283
|
+
const widget = await this._app.commands.execute('terminal:open', {
|
|
284
|
+
name: launched.terminal_name
|
|
285
|
+
});
|
|
226
286
|
if (widget === null || widget === void 0 ? void 0 : widget.id) {
|
|
227
287
|
this._terminalsByPath.set(session.project_path, widget);
|
|
228
288
|
this._wireTerminalDisposal(session.project_path, widget);
|
|
229
289
|
}
|
|
230
|
-
const term = (_b = (_a = widget === null || widget === void 0 ? void 0 : widget.content) === null || _a === void 0 ? void 0 : _a.session) !== null && _b !== void 0 ? _b : widget === null || widget === void 0 ? void 0 : widget.session;
|
|
231
|
-
const command = `cd ${this._shellQuote(session.project_path)} && claude --resume ${session.session_id}\r`;
|
|
232
|
-
if (!term || typeof term.send !== 'function') {
|
|
233
|
-
this._statusEl.textContent = `Run in a terminal: cd ${session.project_path} && claude --resume ${session.session_id}`;
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
// Wait for the first message from the terminal (shell prompt) before
|
|
237
|
-
// injecting input - sending too early arrives before the shell is ready.
|
|
238
|
-
let sent = false;
|
|
239
|
-
const send = () => {
|
|
240
|
-
if (sent) {
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
sent = true;
|
|
244
|
-
term.send({ type: 'stdin', content: [command] });
|
|
245
|
-
};
|
|
246
|
-
if ((_c = term.messageReceived) === null || _c === void 0 ? void 0 : _c.connect) {
|
|
247
|
-
const handler = () => {
|
|
248
|
-
term.messageReceived.disconnect(handler);
|
|
249
|
-
// Tiny additional delay so the prompt is rendered and ready for input
|
|
250
|
-
setTimeout(send, 150);
|
|
251
|
-
};
|
|
252
|
-
term.messageReceived.connect(handler);
|
|
253
|
-
// Hard fallback: if no message arrives within 2s, send anyway
|
|
254
|
-
setTimeout(send, 2000);
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
setTimeout(send, 600);
|
|
258
|
-
}
|
|
259
290
|
}
|
|
260
291
|
catch (err) {
|
|
261
292
|
this._showError(err);
|
|
262
293
|
}
|
|
263
294
|
}
|
|
264
|
-
_shellQuote(s) {
|
|
265
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
266
|
-
}
|
|
267
295
|
async _findTerminalForCwd(projectPath) {
|
|
268
296
|
var _a, _b;
|
|
269
297
|
if (!this._terminalTracker) {
|
|
@@ -378,13 +406,15 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
378
406
|
this._bodyEl.appendChild(empty);
|
|
379
407
|
return;
|
|
380
408
|
}
|
|
381
|
-
// Compute disambiguated display names once per render
|
|
409
|
+
// Compute disambiguated display names once per render (against the
|
|
410
|
+
// full set so suffixes stay stable when filtering narrows the view).
|
|
382
411
|
this._displayNames = this._disambiguate(sessions);
|
|
383
|
-
const
|
|
384
|
-
const
|
|
412
|
+
const filtered = sessions.filter(s => this._matchesFilter(s));
|
|
413
|
+
const favourites = filtered.filter(s => s.favourite);
|
|
414
|
+
const recent = [...filtered]
|
|
385
415
|
.sort((a, b) => b.file_mtime - a.file_mtime)
|
|
386
|
-
.slice(0,
|
|
387
|
-
const all = [...
|
|
416
|
+
.slice(0, this._recentLimit);
|
|
417
|
+
const all = [...filtered].sort((a, b) => this._lookupName(a).localeCompare(this._lookupName(b)));
|
|
388
418
|
if (favourites.length > 0) {
|
|
389
419
|
this._renderSection('favourites', favourites);
|
|
390
420
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab_claude_code_extension",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"description": "Browse, resume, and manage your Claude Code CLI sessions from a JupyterLab side panel. One click reactivates the right terminal - no duplicate tabs, live remote-control indicator, and favourites for the projects you keep coming back to.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -56,7 +56,6 @@
|
|
|
56
56
|
"watch:labextension": "jupyter labextension watch ."
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@jupyterlab/application": "^4.0.0",
|
|
60
59
|
"@jupyterlab/apputils": "^4.0.0",
|
|
61
60
|
"@jupyterlab/coreutils": "^6.0.0",
|
|
62
61
|
"@jupyterlab/services": "^7.0.0",
|
package/src/index.ts
CHANGED
|
@@ -62,6 +62,10 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
62
62
|
const resolve = settings.get('resolveSessionNames')
|
|
63
63
|
.composite as boolean;
|
|
64
64
|
widget.setResolveSessionNames(resolve !== false);
|
|
65
|
+
const limit = settings.get('recentLimit').composite as number;
|
|
66
|
+
if (typeof limit === 'number') {
|
|
67
|
+
widget.setRecentLimit(limit);
|
|
68
|
+
}
|
|
65
69
|
};
|
|
66
70
|
apply();
|
|
67
71
|
settings.changed.connect(apply);
|
package/src/types.ts
CHANGED
|
@@ -40,3 +40,12 @@ export interface IRemoveRequest {
|
|
|
40
40
|
export interface IRemoveResponse {
|
|
41
41
|
removed: string;
|
|
42
42
|
}
|
|
43
|
+
|
|
44
|
+
export interface ILaunchTerminalRequest {
|
|
45
|
+
project_path: string;
|
|
46
|
+
session_id: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ILaunchTerminalResponse {
|
|
50
|
+
terminal_name: string;
|
|
51
|
+
}
|
package/src/widget.ts
CHANGED
|
@@ -9,13 +9,14 @@ import { requestAPI } from './request';
|
|
|
9
9
|
import { claudeIcon, refreshIcon, removeIcon, starFilledIcon } from './icons';
|
|
10
10
|
import {
|
|
11
11
|
IFavouriteResponse,
|
|
12
|
+
ILaunchTerminalResponse,
|
|
12
13
|
IRemoveResponse,
|
|
13
14
|
ISession,
|
|
14
15
|
ISessionsListResponse
|
|
15
16
|
} from './types';
|
|
16
17
|
|
|
17
18
|
const POLL_INTERVAL_MS = 10_000;
|
|
18
|
-
const
|
|
19
|
+
const DEFAULT_RECENT_LIMIT = 10;
|
|
19
20
|
const EXPANDED_STORAGE_KEY = 'jupyterlab_claude_code_extension:expanded';
|
|
20
21
|
|
|
21
22
|
type SectionKey = 'favourites' | 'recent' | 'all';
|
|
@@ -114,6 +115,16 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
114
115
|
this._render();
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
/** Set how many rows the Recent section displays. */
|
|
119
|
+
setRecentLimit(n: number): void {
|
|
120
|
+
const clamped = Math.max(1, Math.min(100, Math.floor(n)));
|
|
121
|
+
if (this._recentLimit === clamped) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this._recentLimit = clamped;
|
|
125
|
+
this._render();
|
|
126
|
+
}
|
|
127
|
+
|
|
117
128
|
protected onAfterShow(_msg: Message): void {
|
|
118
129
|
this.refresh();
|
|
119
130
|
this._startPolling();
|
|
@@ -150,6 +161,16 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
150
161
|
header.appendChild(refreshBtn);
|
|
151
162
|
this._refreshBtn = refreshBtn;
|
|
152
163
|
|
|
164
|
+
const search = document.createElement('input');
|
|
165
|
+
search.type = 'search';
|
|
166
|
+
search.className = 'jp-ClaudeSessionsPanel-search';
|
|
167
|
+
search.placeholder = 'Filter sessions...';
|
|
168
|
+
search.spellcheck = false;
|
|
169
|
+
search.addEventListener('input', () => {
|
|
170
|
+
this._filter = search.value;
|
|
171
|
+
this._render();
|
|
172
|
+
});
|
|
173
|
+
|
|
153
174
|
const body = document.createElement('div');
|
|
154
175
|
body.className = 'jp-ClaudeSessionsPanel-body';
|
|
155
176
|
|
|
@@ -157,6 +178,7 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
157
178
|
status.className = 'jp-ClaudeSessionsPanel-status';
|
|
158
179
|
|
|
159
180
|
root.appendChild(header);
|
|
181
|
+
root.appendChild(search);
|
|
160
182
|
root.appendChild(body);
|
|
161
183
|
root.appendChild(status);
|
|
162
184
|
|
|
@@ -164,6 +186,37 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
164
186
|
this._statusEl = status;
|
|
165
187
|
}
|
|
166
188
|
|
|
189
|
+
/** Lowercase substring + subsequence match. */
|
|
190
|
+
private _fuzzyMatch(haystack: string, needle: string): boolean {
|
|
191
|
+
if (!needle) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
const h = haystack.toLowerCase();
|
|
195
|
+
const n = needle.toLowerCase();
|
|
196
|
+
if (h.includes(n)) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
let j = 0;
|
|
200
|
+
for (let i = 0; i < h.length && j < n.length; i++) {
|
|
201
|
+
if (h[i] === n[j]) {
|
|
202
|
+
j += 1;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return j === n.length;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private _matchesFilter(s: ISession): boolean {
|
|
209
|
+
const q = this._filter.trim();
|
|
210
|
+
if (!q) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
return (
|
|
214
|
+
this._fuzzyMatch(s.name, q) ||
|
|
215
|
+
this._fuzzyMatch(s.project_path, q) ||
|
|
216
|
+
this._fuzzyMatch(this._lookupName(s), q)
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
167
220
|
private _showLoading(): void {
|
|
168
221
|
this._statusEl.textContent = this._sessions === null ? 'Loading...' : '';
|
|
169
222
|
}
|
|
@@ -272,57 +325,34 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
272
325
|
return;
|
|
273
326
|
}
|
|
274
327
|
|
|
275
|
-
// 3. No matching
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
328
|
+
// 3. No matching terminal - spawn a new one with `claude --resume <id>`
|
|
329
|
+
// as the pty's only process (no shell). Server-side endpoint calls
|
|
330
|
+
// terminal_manager.create(shell_command=[claude, --resume, sid], cwd=...)
|
|
331
|
+
// and returns the terminal name; we then attach JL's standard widget
|
|
332
|
+
// via terminal:open. When claude exits, the tab closes.
|
|
333
|
+
const launched = await requestAPI<ILaunchTerminalResponse>(
|
|
334
|
+
'launch-terminal',
|
|
335
|
+
this._serverSettings,
|
|
336
|
+
{
|
|
337
|
+
method: 'POST',
|
|
338
|
+
body: JSON.stringify({
|
|
339
|
+
project_path: session.project_path,
|
|
340
|
+
session_id: session.session_id
|
|
341
|
+
})
|
|
342
|
+
}
|
|
279
343
|
);
|
|
280
|
-
|
|
344
|
+
const widget: any = await this._app.commands.execute('terminal:open', {
|
|
345
|
+
name: launched.terminal_name
|
|
346
|
+
});
|
|
281
347
|
if (widget?.id) {
|
|
282
348
|
this._terminalsByPath.set(session.project_path, widget);
|
|
283
349
|
this._wireTerminalDisposal(session.project_path, widget);
|
|
284
350
|
}
|
|
285
|
-
|
|
286
|
-
const term = widget?.content?.session ?? widget?.session;
|
|
287
|
-
const command = `cd ${this._shellQuote(session.project_path)} && claude --resume ${session.session_id}\r`;
|
|
288
|
-
|
|
289
|
-
if (!term || typeof term.send !== 'function') {
|
|
290
|
-
this._statusEl.textContent = `Run in a terminal: cd ${session.project_path} && claude --resume ${session.session_id}`;
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Wait for the first message from the terminal (shell prompt) before
|
|
295
|
-
// injecting input - sending too early arrives before the shell is ready.
|
|
296
|
-
let sent = false;
|
|
297
|
-
const send = () => {
|
|
298
|
-
if (sent) {
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
sent = true;
|
|
302
|
-
term.send({ type: 'stdin', content: [command] });
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
if (term.messageReceived?.connect) {
|
|
306
|
-
const handler = () => {
|
|
307
|
-
term.messageReceived.disconnect(handler);
|
|
308
|
-
// Tiny additional delay so the prompt is rendered and ready for input
|
|
309
|
-
setTimeout(send, 150);
|
|
310
|
-
};
|
|
311
|
-
term.messageReceived.connect(handler);
|
|
312
|
-
// Hard fallback: if no message arrives within 2s, send anyway
|
|
313
|
-
setTimeout(send, 2000);
|
|
314
|
-
} else {
|
|
315
|
-
setTimeout(send, 600);
|
|
316
|
-
}
|
|
317
351
|
} catch (err) {
|
|
318
352
|
this._showError(err);
|
|
319
353
|
}
|
|
320
354
|
}
|
|
321
355
|
|
|
322
|
-
private _shellQuote(s: string): string {
|
|
323
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
356
|
private async _findTerminalForCwd(projectPath: string): Promise<any | null> {
|
|
327
357
|
if (!this._terminalTracker) {
|
|
328
358
|
return null;
|
|
@@ -447,14 +477,16 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
447
477
|
return;
|
|
448
478
|
}
|
|
449
479
|
|
|
450
|
-
// Compute disambiguated display names once per render
|
|
480
|
+
// Compute disambiguated display names once per render (against the
|
|
481
|
+
// full set so suffixes stay stable when filtering narrows the view).
|
|
451
482
|
this._displayNames = this._disambiguate(sessions);
|
|
452
483
|
|
|
453
|
-
const
|
|
454
|
-
const
|
|
484
|
+
const filtered = sessions.filter(s => this._matchesFilter(s));
|
|
485
|
+
const favourites = filtered.filter(s => s.favourite);
|
|
486
|
+
const recent = [...filtered]
|
|
455
487
|
.sort((a, b) => b.file_mtime - a.file_mtime)
|
|
456
|
-
.slice(0,
|
|
457
|
-
const all = [...
|
|
488
|
+
.slice(0, this._recentLimit);
|
|
489
|
+
const all = [...filtered].sort((a, b) =>
|
|
458
490
|
this._lookupName(a).localeCompare(this._lookupName(b))
|
|
459
491
|
);
|
|
460
492
|
|
|
@@ -760,5 +792,7 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
760
792
|
private readonly _pendingByPath: Map<string, Promise<void>> = new Map();
|
|
761
793
|
private readonly _rootDir: string;
|
|
762
794
|
private _resolveNames: boolean = true;
|
|
795
|
+
private _recentLimit: number = DEFAULT_RECENT_LIMIT;
|
|
763
796
|
private _displayNames: Map<string, string> = new Map();
|
|
797
|
+
private _filter: string = '';
|
|
764
798
|
}
|
package/style/base.css
CHANGED
|
@@ -57,6 +57,29 @@
|
|
|
57
57
|
height: 14px;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
.jp-ClaudeSessionsPanel-search {
|
|
61
|
+
flex: 0 0 auto;
|
|
62
|
+
margin: 4px 8px;
|
|
63
|
+
padding: 2px 6px;
|
|
64
|
+
background: var(--jp-layout-color1);
|
|
65
|
+
color: var(--jp-ui-font-color1);
|
|
66
|
+
border: 1px solid var(--jp-border-color2);
|
|
67
|
+
border-radius: 2px;
|
|
68
|
+
font-family: inherit;
|
|
69
|
+
font-size: var(--jp-ui-font-size1);
|
|
70
|
+
outline: none;
|
|
71
|
+
-webkit-appearance: none;
|
|
72
|
+
appearance: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.jp-ClaudeSessionsPanel-search:focus {
|
|
76
|
+
border-color: var(--jp-brand-color1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.jp-ClaudeSessionsPanel-search::placeholder {
|
|
80
|
+
color: var(--jp-ui-font-color3);
|
|
81
|
+
}
|
|
82
|
+
|
|
60
83
|
.jp-ClaudeSessionsPanel-body {
|
|
61
84
|
flex: 1 1 auto;
|
|
62
85
|
display: flex;
|