jupyterlab_claude_code_extension 1.2.43 → 1.2.45
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/widget.d.ts +13 -1
- package/lib/widget.js +41 -42
- package/package.json +1 -1
- package/src/__tests__/jupyterlab_claude_code_extension.spec.ts +24 -11
- package/src/widget.ts +41 -46
package/lib/widget.d.ts
CHANGED
|
@@ -114,7 +114,19 @@ export declare class ClaudeCodeSessionsWidget extends Widget {
|
|
|
114
114
|
* matching path so a nested project wins over its parent. Returns null (clear)
|
|
115
115
|
* when no row matches or the matched row has no colour. */
|
|
116
116
|
private _colourForTerminal;
|
|
117
|
-
|
|
117
|
+
/** Find an open terminal running the wanted conversation, or null.
|
|
118
|
+
*
|
|
119
|
+
* Matches on the conversation id ALONE. The backend reads each terminal's
|
|
120
|
+
* true session id from the running claude (`~/.claude/sessions/<pid>.json`),
|
|
121
|
+
* so a bare `claude` / `-c` the user opened is identified too, not only
|
|
122
|
+
* extension launches that carry an argv id. A session id is globally unique
|
|
123
|
+
* to one conversation, so a terminal running it IS the one to focus -
|
|
124
|
+
* regardless of its reported cwd (a claude that cd'd into a subdir, or whose
|
|
125
|
+
* project dir was recreated, must still be reused, not duplicated). Reuse
|
|
126
|
+
* stays strict on identity: a terminal whose running session cannot be read
|
|
127
|
+
* (no claude, or an unreadable id) reports null, which never equals a wanted
|
|
128
|
+
* id, so a DIFFERENT conversation is never focused by mistake (DEF-4). */
|
|
129
|
+
private _findTerminalForSession;
|
|
118
130
|
private _showCloseExistingDialog;
|
|
119
131
|
/** Show a modal with a spinner while the terminal is being launched. The
|
|
120
132
|
* caller must dismiss it via ``.dispose()`` once the work is done - the
|
package/lib/widget.js
CHANGED
|
@@ -520,11 +520,11 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
520
520
|
return;
|
|
521
521
|
}
|
|
522
522
|
// 2. Walk every live terminal widget JL knows about.
|
|
523
|
-
const found = await this.
|
|
523
|
+
const found = await this._findTerminalForSession(session.session_id);
|
|
524
524
|
if (found) {
|
|
525
525
|
// Tag the cache with the OBSERVED conversation - here it equals the
|
|
526
|
-
// wanted id (the gate in
|
|
527
|
-
// a confirmed conversation rather than a wish.
|
|
526
|
+
// wanted id (the gate in _findTerminalForSession), so a later reuse
|
|
527
|
+
// trusts a confirmed conversation rather than a wish.
|
|
528
528
|
this._terminalsByPath.set(session.project_path, {
|
|
529
529
|
widget: found.widget,
|
|
530
530
|
sessionId: (_a = found.runningId) !== null && _a !== void 0 ? _a : undefined
|
|
@@ -758,9 +758,21 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
758
758
|
}
|
|
759
759
|
return best ? ((_b = best.color) !== null && _b !== void 0 ? _b : null) : null;
|
|
760
760
|
}
|
|
761
|
-
|
|
761
|
+
/** Find an open terminal running the wanted conversation, or null.
|
|
762
|
+
*
|
|
763
|
+
* Matches on the conversation id ALONE. The backend reads each terminal's
|
|
764
|
+
* true session id from the running claude (`~/.claude/sessions/<pid>.json`),
|
|
765
|
+
* so a bare `claude` / `-c` the user opened is identified too, not only
|
|
766
|
+
* extension launches that carry an argv id. A session id is globally unique
|
|
767
|
+
* to one conversation, so a terminal running it IS the one to focus -
|
|
768
|
+
* regardless of its reported cwd (a claude that cd'd into a subdir, or whose
|
|
769
|
+
* project dir was recreated, must still be reused, not duplicated). Reuse
|
|
770
|
+
* stays strict on identity: a terminal whose running session cannot be read
|
|
771
|
+
* (no claude, or an unreadable id) reports null, which never equals a wanted
|
|
772
|
+
* id, so a DIFFERENT conversation is never focused by mistake (DEF-4). */
|
|
773
|
+
async _findTerminalForSession(wantedSessionId) {
|
|
762
774
|
var _a, _b, _c;
|
|
763
|
-
if (!this._terminalTracker) {
|
|
775
|
+
if (!this._terminalTracker || !wantedSessionId) {
|
|
764
776
|
return null;
|
|
765
777
|
}
|
|
766
778
|
const candidates = [];
|
|
@@ -769,7 +781,6 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
769
781
|
candidates.push(widget);
|
|
770
782
|
}
|
|
771
783
|
});
|
|
772
|
-
const target = projectPath.replace(/\/+$/, '');
|
|
773
784
|
for (const widget of candidates) {
|
|
774
785
|
const sessName = (_b = (_a = widget === null || widget === void 0 ? void 0 : widget.content) === null || _a === void 0 ? void 0 : _a.session) === null || _b === void 0 ? void 0 : _b.name;
|
|
775
786
|
if (typeof sessName !== 'string' || !sessName) {
|
|
@@ -777,33 +788,13 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
777
788
|
}
|
|
778
789
|
try {
|
|
779
790
|
const data = await requestAPI(`terminal-cwd/${encodeURIComponent(sessName)}`, this._serverSettings);
|
|
780
|
-
//
|
|
781
|
-
// a
|
|
782
|
-
//
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
// is running the wanted conversation. A terminal whose conversation
|
|
788
|
-
// is unknown (claude started with -c/--continue or a bare claude, so
|
|
789
|
-
// its argv carries no id) is never reused - it may be running a
|
|
790
|
-
// different conversation of this project (the switch-then-click bug).
|
|
791
|
-
// Extension launches always carry an explicit id, so an unknown
|
|
792
|
-
// terminal is necessarily foreign.
|
|
793
|
-
const runningId = (_c = data.session_id) !== null && _c !== void 0 ? _c : null;
|
|
794
|
-
// A missing wanted id (undefined) or an unknown running id (null) can
|
|
795
|
-
// never be a positive match - guard so neither side's "absent" value
|
|
796
|
-
// is mistaken for equality and an unknown terminal slips through.
|
|
797
|
-
if (!wantedSessionId || runningId !== wantedSessionId) {
|
|
798
|
-
continue;
|
|
799
|
-
}
|
|
800
|
-
const cwds = Array.isArray(data === null || data === void 0 ? void 0 : data.cwds) ? data.cwds : [];
|
|
801
|
-
for (const cwd of cwds) {
|
|
802
|
-
if ((cwd || '').replace(/\/+$/, '') === target) {
|
|
803
|
-
// runningId equals the wanted id here (gated above), so the caller
|
|
804
|
-
// tags its cache with a confirmed conversation id.
|
|
805
|
-
return { widget, runningId };
|
|
806
|
-
}
|
|
791
|
+
// Positive identity match on the running conversation. The backend
|
|
792
|
+
// reports a session id only for a live claude, so a match implies
|
|
793
|
+
// has_claude - the id alone is the gate, and the terminal's cwd is
|
|
794
|
+
// irrelevant once its conversation is positively known.
|
|
795
|
+
const runningId = (_c = data === null || data === void 0 ? void 0 : data.session_id) !== null && _c !== void 0 ? _c : null;
|
|
796
|
+
if (runningId === wantedSessionId) {
|
|
797
|
+
return { widget, runningId };
|
|
807
798
|
}
|
|
808
799
|
}
|
|
809
800
|
catch (_err) {
|
|
@@ -838,9 +829,11 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
838
829
|
body,
|
|
839
830
|
buttons: []
|
|
840
831
|
});
|
|
841
|
-
// launch() returns a Promise we don't await -
|
|
842
|
-
// when the spawn completes
|
|
843
|
-
|
|
832
|
+
// launch() returns a Promise we don't await - the caller dismisses this
|
|
833
|
+
// dialog with .dispose() when the spawn completes. Disposing an open Lumino
|
|
834
|
+
// dialog rejects that promise with `undefined`, so catch it here to keep a
|
|
835
|
+
// benign teardown from surfacing as an unhandled promise rejection.
|
|
836
|
+
dialog.launch().catch(() => undefined);
|
|
844
837
|
return dialog;
|
|
845
838
|
}
|
|
846
839
|
_wireTerminalDisposal(projectPath, widget) {
|
|
@@ -1242,7 +1235,9 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
1242
1235
|
Notification.warning('Folder is outside the JupyterLab root - cannot open a terminal there.', { autoClose: 4000 });
|
|
1243
1236
|
return;
|
|
1244
1237
|
}
|
|
1245
|
-
|
|
1238
|
+
this._app.commands
|
|
1239
|
+
.execute('terminal:create-new', { cwd: rel })
|
|
1240
|
+
.catch(err => this._showError(err));
|
|
1246
1241
|
}
|
|
1247
1242
|
});
|
|
1248
1243
|
this._commands.addCommand('claude-code-sessions:show-in-filebrowser', {
|
|
@@ -1261,9 +1256,9 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
1261
1256
|
}
|
|
1262
1257
|
// JL's built-in command navigates the default file browser to the
|
|
1263
1258
|
// path and reveals the browser panel.
|
|
1264
|
-
|
|
1265
|
-
path: rel
|
|
1266
|
-
|
|
1259
|
+
this._app.commands
|
|
1260
|
+
.execute('filebrowser:go-to-path', { path: rel })
|
|
1261
|
+
.catch(err => this._showError(err));
|
|
1267
1262
|
}
|
|
1268
1263
|
});
|
|
1269
1264
|
this._commands.addCommand('claude-code-sessions:copy-path', {
|
|
@@ -1831,7 +1826,9 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
1831
1826
|
});
|
|
1832
1827
|
render();
|
|
1833
1828
|
updateControls();
|
|
1834
|
-
|
|
1829
|
+
// dialog.dispose() (on open/switch) rejects this promise with `undefined`;
|
|
1830
|
+
// catch it so the teardown never surfaces as an unhandled promise rejection.
|
|
1831
|
+
dialog.launch().catch(() => undefined);
|
|
1835
1832
|
search.focus();
|
|
1836
1833
|
}
|
|
1837
1834
|
/** Delete the given branch sessions of the active row's project.
|
|
@@ -1961,7 +1958,9 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
1961
1958
|
: `Branch switch failed: ${err}`, { autoClose: 4000 });
|
|
1962
1959
|
}
|
|
1963
1960
|
finally {
|
|
1964
|
-
|
|
1961
|
+
// Best-effort refresh - never let a transient fetch failure reject this
|
|
1962
|
+
// fire-and-forget switch (its callers do not await it).
|
|
1963
|
+
await this._fetch().catch(() => undefined);
|
|
1965
1964
|
}
|
|
1966
1965
|
}
|
|
1967
1966
|
/** Open a specific conversation branch in its own terminal.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab_claude_code_extension",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.45",
|
|
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",
|
|
@@ -522,13 +522,15 @@ describe('launch spinner dismiss contract', () => {
|
|
|
522
522
|
* Contract for conversation-aware terminal reuse and the Open Branched
|
|
523
523
|
* Conversation feature. Reuse must refuse to focus a terminal running a
|
|
524
524
|
* DIFFERENT or UNKNOWN conversation (the switch-then-click bug, DEF-4): a
|
|
525
|
-
* terminal is reused only on a POSITIVE conversation-id match.
|
|
526
|
-
*
|
|
527
|
-
*
|
|
525
|
+
* terminal is reused only on a POSITIVE conversation-id match. The backend
|
|
526
|
+
* reads each terminal's true session id from the running claude's own
|
|
527
|
+
* `~/.claude/sessions/<pid>.json`, so a terminal the user opened with a bare
|
|
528
|
+
* `claude` / `-c` is identifiable too - reuse is not limited to extension
|
|
529
|
+
* launches that carry an id in argv.
|
|
528
530
|
*/
|
|
529
531
|
describe('conversation-aware reuse + open-branch contract', () => {
|
|
530
532
|
const findTerm = (widgetSrc.match(
|
|
531
|
-
/private async
|
|
533
|
+
/private async _findTerminalForSession[\s\S]*?\n \}/
|
|
532
534
|
) ?? [''])[0];
|
|
533
535
|
const doResume = (widgetSrc.match(
|
|
534
536
|
/private async _doResumeInTerminal[\s\S]*?\n \}/
|
|
@@ -553,19 +555,30 @@ describe('launch spinner dismiss contract', () => {
|
|
|
553
555
|
expect(iface).toMatch(/session_id\?: string \| null/);
|
|
554
556
|
});
|
|
555
557
|
|
|
556
|
-
it('
|
|
558
|
+
it('_findTerminalForSession keys on the session id alone (no cwd param)', () => {
|
|
557
559
|
expect(findTerm).toMatch(
|
|
558
|
-
/
|
|
560
|
+
/_findTerminalForSession\(\s*wantedSessionId: string \| undefined\s*\)/
|
|
559
561
|
);
|
|
562
|
+
// A missing wanted id short-circuits so null never matches null.
|
|
563
|
+
expect(findTerm).toMatch(
|
|
564
|
+
/if \(!this\._terminalTracker \|\| !wantedSessionId\)/
|
|
565
|
+
);
|
|
566
|
+
// Reuse is identity-only now - it must NOT gate on the terminal's cwd
|
|
567
|
+
// (a claude cd'd into a subdir, or a recreated project dir, must still
|
|
568
|
+
// be reused once its conversation is positively known).
|
|
569
|
+
expect(findTerm).not.toMatch(/cwds/);
|
|
570
|
+
expect(findTerm).not.toMatch(/=== target/);
|
|
560
571
|
});
|
|
561
572
|
|
|
562
573
|
it('reuse requires a positive conversation-id match (no lenient unknown reuse)', () => {
|
|
563
|
-
expect(findTerm).toMatch(/const runningId = data\.session_id \?\? null/);
|
|
564
|
-
// The ONLY reuse condition: a truthy wanted id equal to the observed
|
|
565
|
-
// id. A missing wanted id or an unknown (null) running id never matches,
|
|
566
|
-
// so a -c/--continue or bare-claude terminal is never reused (DEF-4).
|
|
567
574
|
expect(findTerm).toMatch(
|
|
568
|
-
/
|
|
575
|
+
/const runningId = data\?\.session_id \?\? null/
|
|
576
|
+
);
|
|
577
|
+
// The ONLY reuse condition: the observed running id equals the wanted
|
|
578
|
+
// id. An unknown (null) running id never equals a truthy wanted id, so a
|
|
579
|
+
// terminal whose conversation cannot be read is never reused (DEF-4).
|
|
580
|
+
expect(findTerm).toMatch(
|
|
581
|
+
/if \(runningId === wantedSessionId\) \{\s*return \{ widget, runningId \};/
|
|
569
582
|
);
|
|
570
583
|
// The old lenient/strict machinery is gone.
|
|
571
584
|
expect(findTerm).not.toMatch(/unknownConversation/);
|
package/src/widget.ts
CHANGED
|
@@ -635,14 +635,11 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
635
635
|
}
|
|
636
636
|
|
|
637
637
|
// 2. Walk every live terminal widget JL knows about.
|
|
638
|
-
const found = await this.
|
|
639
|
-
session.project_path,
|
|
640
|
-
session.session_id
|
|
641
|
-
);
|
|
638
|
+
const found = await this._findTerminalForSession(session.session_id);
|
|
642
639
|
if (found) {
|
|
643
640
|
// Tag the cache with the OBSERVED conversation - here it equals the
|
|
644
|
-
// wanted id (the gate in
|
|
645
|
-
// a confirmed conversation rather than a wish.
|
|
641
|
+
// wanted id (the gate in _findTerminalForSession), so a later reuse
|
|
642
|
+
// trusts a confirmed conversation rather than a wish.
|
|
646
643
|
this._terminalsByPath.set(session.project_path, {
|
|
647
644
|
widget: found.widget,
|
|
648
645
|
sessionId: found.runningId ?? undefined
|
|
@@ -901,11 +898,22 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
901
898
|
return best ? (best.color ?? null) : null;
|
|
902
899
|
}
|
|
903
900
|
|
|
904
|
-
|
|
905
|
-
|
|
901
|
+
/** Find an open terminal running the wanted conversation, or null.
|
|
902
|
+
*
|
|
903
|
+
* Matches on the conversation id ALONE. The backend reads each terminal's
|
|
904
|
+
* true session id from the running claude (`~/.claude/sessions/<pid>.json`),
|
|
905
|
+
* so a bare `claude` / `-c` the user opened is identified too, not only
|
|
906
|
+
* extension launches that carry an argv id. A session id is globally unique
|
|
907
|
+
* to one conversation, so a terminal running it IS the one to focus -
|
|
908
|
+
* regardless of its reported cwd (a claude that cd'd into a subdir, or whose
|
|
909
|
+
* project dir was recreated, must still be reused, not duplicated). Reuse
|
|
910
|
+
* stays strict on identity: a terminal whose running session cannot be read
|
|
911
|
+
* (no claude, or an unreadable id) reports null, which never equals a wanted
|
|
912
|
+
* id, so a DIFFERENT conversation is never focused by mistake (DEF-4). */
|
|
913
|
+
private async _findTerminalForSession(
|
|
906
914
|
wantedSessionId: string | undefined
|
|
907
915
|
): Promise<{ widget: any; runningId: string | null } | null> {
|
|
908
|
-
if (!this._terminalTracker) {
|
|
916
|
+
if (!this._terminalTracker || !wantedSessionId) {
|
|
909
917
|
return null;
|
|
910
918
|
}
|
|
911
919
|
const candidates: any[] = [];
|
|
@@ -914,7 +922,6 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
914
922
|
candidates.push(widget);
|
|
915
923
|
}
|
|
916
924
|
});
|
|
917
|
-
const target = projectPath.replace(/\/+$/, '');
|
|
918
925
|
for (const widget of candidates) {
|
|
919
926
|
const sessName: string | undefined = widget?.content?.session?.name;
|
|
920
927
|
if (typeof sessName !== 'string' || !sessName) {
|
|
@@ -925,33 +932,13 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
925
932
|
`terminal-cwd/${encodeURIComponent(sessName)}`,
|
|
926
933
|
this._serverSettings
|
|
927
934
|
);
|
|
928
|
-
//
|
|
929
|
-
// a
|
|
930
|
-
//
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
// is running the wanted conversation. A terminal whose conversation
|
|
936
|
-
// is unknown (claude started with -c/--continue or a bare claude, so
|
|
937
|
-
// its argv carries no id) is never reused - it may be running a
|
|
938
|
-
// different conversation of this project (the switch-then-click bug).
|
|
939
|
-
// Extension launches always carry an explicit id, so an unknown
|
|
940
|
-
// terminal is necessarily foreign.
|
|
941
|
-
const runningId = data.session_id ?? null;
|
|
942
|
-
// A missing wanted id (undefined) or an unknown running id (null) can
|
|
943
|
-
// never be a positive match - guard so neither side's "absent" value
|
|
944
|
-
// is mistaken for equality and an unknown terminal slips through.
|
|
945
|
-
if (!wantedSessionId || runningId !== wantedSessionId) {
|
|
946
|
-
continue;
|
|
947
|
-
}
|
|
948
|
-
const cwds = Array.isArray(data?.cwds) ? data.cwds : [];
|
|
949
|
-
for (const cwd of cwds) {
|
|
950
|
-
if ((cwd || '').replace(/\/+$/, '') === target) {
|
|
951
|
-
// runningId equals the wanted id here (gated above), so the caller
|
|
952
|
-
// tags its cache with a confirmed conversation id.
|
|
953
|
-
return { widget, runningId };
|
|
954
|
-
}
|
|
935
|
+
// Positive identity match on the running conversation. The backend
|
|
936
|
+
// reports a session id only for a live claude, so a match implies
|
|
937
|
+
// has_claude - the id alone is the gate, and the terminal's cwd is
|
|
938
|
+
// irrelevant once its conversation is positively known.
|
|
939
|
+
const runningId = data?.session_id ?? null;
|
|
940
|
+
if (runningId === wantedSessionId) {
|
|
941
|
+
return { widget, runningId };
|
|
955
942
|
}
|
|
956
943
|
} catch (_err) {
|
|
957
944
|
// Backend may report 404 for terminals that disappeared between
|
|
@@ -990,9 +977,11 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
990
977
|
body,
|
|
991
978
|
buttons: []
|
|
992
979
|
});
|
|
993
|
-
// launch() returns a Promise we don't await -
|
|
994
|
-
// when the spawn completes
|
|
995
|
-
|
|
980
|
+
// launch() returns a Promise we don't await - the caller dismisses this
|
|
981
|
+
// dialog with .dispose() when the spawn completes. Disposing an open Lumino
|
|
982
|
+
// dialog rejects that promise with `undefined`, so catch it here to keep a
|
|
983
|
+
// benign teardown from surfacing as an unhandled promise rejection.
|
|
984
|
+
dialog.launch().catch(() => undefined);
|
|
996
985
|
return dialog;
|
|
997
986
|
}
|
|
998
987
|
|
|
@@ -1440,7 +1429,9 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
1440
1429
|
);
|
|
1441
1430
|
return;
|
|
1442
1431
|
}
|
|
1443
|
-
|
|
1432
|
+
this._app.commands
|
|
1433
|
+
.execute('terminal:create-new', { cwd: rel })
|
|
1434
|
+
.catch(err => this._showError(err));
|
|
1444
1435
|
}
|
|
1445
1436
|
});
|
|
1446
1437
|
|
|
@@ -1463,9 +1454,9 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
1463
1454
|
}
|
|
1464
1455
|
// JL's built-in command navigates the default file browser to the
|
|
1465
1456
|
// path and reveals the browser panel.
|
|
1466
|
-
|
|
1467
|
-
path: rel
|
|
1468
|
-
|
|
1457
|
+
this._app.commands
|
|
1458
|
+
.execute('filebrowser:go-to-path', { path: rel })
|
|
1459
|
+
.catch(err => this._showError(err));
|
|
1469
1460
|
}
|
|
1470
1461
|
});
|
|
1471
1462
|
|
|
@@ -2093,7 +2084,9 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
2093
2084
|
render();
|
|
2094
2085
|
updateControls();
|
|
2095
2086
|
|
|
2096
|
-
|
|
2087
|
+
// dialog.dispose() (on open/switch) rejects this promise with `undefined`;
|
|
2088
|
+
// catch it so the teardown never surfaces as an unhandled promise rejection.
|
|
2089
|
+
dialog.launch().catch(() => undefined);
|
|
2097
2090
|
search.focus();
|
|
2098
2091
|
}
|
|
2099
2092
|
|
|
@@ -2240,7 +2233,9 @@ export class ClaudeCodeSessionsWidget extends Widget {
|
|
|
2240
2233
|
{ autoClose: 4000 }
|
|
2241
2234
|
);
|
|
2242
2235
|
} finally {
|
|
2243
|
-
|
|
2236
|
+
// Best-effort refresh - never let a transient fetch failure reject this
|
|
2237
|
+
// fire-and-forget switch (its callers do not await it).
|
|
2238
|
+
await this._fetch().catch(() => undefined);
|
|
2244
2239
|
}
|
|
2245
2240
|
}
|
|
2246
2241
|
|