jupyterlab_ai_code_assistants_extension 1.2.35 → 1.2.38

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
@@ -41,7 +41,7 @@ Chat-panel extensions re-implement the agent loop and trail the real tool. This
41
41
  - **Favorites** - star projects you keep coming back to via the right-click menu; favourites from the standalone extensions are migrated on first run
42
42
  - **Remove and clean up** - drop a project's history or a project's extra parallel sessions from the right-click menu, confirmation dialog first; removed files honour JupyterLab's "move files to trash" setting
43
43
  - **Activity at a glance** - each row shows its last activity in an aligned column; rows active within the last minute light up, rows idle for over a week dim, and rows with parallel conversations show a branch icon with the count
44
- - **Remote control indicator and background agents** (Claude) - a green dot marks sessions actively under remote control, and a conversation held by a running background agent shows a `bg` chip; clicking attaches to the agent instead of copying it
44
+ - **Remote control indicator and background agents** (Claude) - a green dot marks sessions actively under remote control, and a conversation held by a running background agent shows a `bg` chip; clicking stops the agent and resumes the same conversation in your terminal, so it becomes an ordinary interactive session
45
45
  - **Search** - fuzzy filter per panel, toggled by the funnel button
46
46
  - **Presentation modes** - label rows by session name or by path relative to the JupyterLab root
47
47
  - **Conflict-safe upgrade** - if a retired standalone extension is still installed, its panel wins and this extension stands down for that assistant instead of showing a duplicate
@@ -252,24 +252,6 @@ export declare class AssistantSessionsPanel extends Widget {
252
252
  * that build a byte-identical launch, and the unmarked one is the default
253
253
  * the user clicks. */
254
254
  private _resolvedVariant;
255
- /** The same mode as it applies to RESUME - of the active row by default, or
256
- * of whichever conversation is named.
257
- *
258
- * An attach is not a launch: the server returns the argv before the flag is
259
- * appended, deliberately, so promising the mode on a conversation a live
260
- * worker already holds would name something the assistant never receives.
261
- * That is true of resuming and of nothing else, so it is tested HERE rather
262
- * than in `_resolvedVariant` - which is read by the `+` button and both
263
- * new-session and branch items, none of which attach. Testing it there let
264
- * one right-click on a background-agent row strip the mode's name off every
265
- * one of those surfaces panel-wide, while their launches went on carrying it
266
- * (DEF-37).
267
- *
268
- * The argument is what lets a surface that describes SOMEONE ELSE'S resume -
269
- * a row tooltip built during a render, a branch item in the submenu - ask the
270
- * question about that conversation rather than about the active row.
271
- */
272
- private _resumeVariant;
273
255
  /** The variant suffix a plain item wears while that mode is in force. */
274
256
  private _variantSuffix;
275
257
  /** Whether a forced variant would build the launch the plain item already
@@ -311,9 +293,7 @@ export declare class AssistantSessionsPanel extends Widget {
311
293
  private _openManagePopup;
312
294
  /** Tooltip for one Open button in the Manage Sessions popup. Same rule as the
313
295
  * Open Branched Conversation submenu: the button launches a terminal, so it
314
- * names the mode that launch carries, and a conversation a live worker holds
315
- * is attached to and carries none. The row's own conversation is not in
316
- * `_lastBranches`, so it answers from the session. */
296
+ * names the mode that launch carries. */
317
297
  private _openBranchTitle;
318
298
  /** The live-worker chip for a branch row, when the provider has workers at
319
299
  * all. Returning null lets the popup fall back to its fork marker. */
package/lib/core/panel.js CHANGED
@@ -1593,9 +1593,9 @@ export class AssistantSessionsPanel extends Widget {
1593
1593
  badge.appendChild(document.createTextNode(String(session.extra_sessions + 1)));
1594
1594
  name.appendChild(badge);
1595
1595
  }
1596
- // A live worker owns this conversation, so a click joins it rather than
1597
- // resuming a dormant one. Marked so the difference is visible before the
1598
- // click, not a surprise after it. No own title: a child title shadows the
1596
+ // A live worker owns this conversation, so a click stops it before
1597
+ // resuming. Marked so the difference is visible before the click, not a
1598
+ // surprise after it. No own title: a child title shadows the
1599
1599
  // row's, which would hide the path behind a two-character chip.
1600
1600
  if (this._descriptor.hasBgAgents && session.bg_id) {
1601
1601
  const bg = document.createElement('span');
@@ -1767,15 +1767,10 @@ export class AssistantSessionsPanel extends Widget {
1767
1767
  lines.push('Status: active');
1768
1768
  }
1769
1769
  if (this._descriptor.hasBgAgents && s.bg_id) {
1770
- lines.push(`Background agent: ${s.bg_id} (click attaches to it)`);
1770
+ lines.push(`Background agent: ${s.bg_id} (click stops it and resumes here)`);
1771
1771
  }
1772
1772
  lines.push(...((_c = (_b = (_a = this._hooks).tooltipLines) === null || _b === void 0 ? void 0 : _b.call(_a, s)) !== null && _c !== void 0 ? _c : []));
1773
- // What THIS row's click resolves to, which is the attach-aware question:
1774
- // the line above may have just said the click joins a live worker, and an
1775
- // attach is issued before the mode is appended, so `_resolvedVariant`
1776
- // promised a flag the server discards - one tooltip naming both, a line
1777
- // apart (DEF-46).
1778
- const variant = this._resumeVariant(s);
1773
+ const variant = this._resolvedVariant();
1779
1774
  if (variant) {
1780
1775
  // The row click launches with this, and the menu is the only other place
1781
1776
  // that says so.
@@ -1891,27 +1886,6 @@ export class AssistantSessionsPanel extends Widget {
1891
1886
  const resolved = resolvedLaunchModeEntry(this._descriptor.launchModes, this._modes);
1892
1887
  return resolved;
1893
1888
  }
1894
- /** The same mode as it applies to RESUME - of the active row by default, or
1895
- * of whichever conversation is named.
1896
- *
1897
- * An attach is not a launch: the server returns the argv before the flag is
1898
- * appended, deliberately, so promising the mode on a conversation a live
1899
- * worker already holds would name something the assistant never receives.
1900
- * That is true of resuming and of nothing else, so it is tested HERE rather
1901
- * than in `_resolvedVariant` - which is read by the `+` button and both
1902
- * new-session and branch items, none of which attach. Testing it there let
1903
- * one right-click on a background-agent row strip the mode's name off every
1904
- * one of those surfaces panel-wide, while their launches went on carrying it
1905
- * (DEF-37).
1906
- *
1907
- * The argument is what lets a surface that describes SOMEONE ELSE'S resume -
1908
- * a row tooltip built during a render, a branch item in the submenu - ask the
1909
- * question about that conversation rather than about the active row.
1910
- */
1911
- _resumeVariant(target = this._activeSession) {
1912
- const joining = this._descriptor.hasBgAgents && !!(target === null || target === void 0 ? void 0 : target.bg_id);
1913
- return joining ? null : this._resolvedVariant();
1914
- }
1915
1889
  /** The variant suffix a plain item wears while that mode is in force. */
1916
1890
  _variantSuffix() {
1917
1891
  const resolved = this._resolvedVariant();
@@ -1951,16 +1925,15 @@ export class AssistantSessionsPanel extends Widget {
1951
1925
  }
1952
1926
  });
1953
1927
  this._commands.addCommand(this._cmd('resume'), {
1954
- // Joining a live worker and resuming a dormant conversation are
1955
- // different verbs, so a provider that has both says which one this row
1956
- // gets.
1928
+ // Resuming a conversation a live worker holds stops that worker first,
1929
+ // so a provider that has workers says which of the two this row gets.
1957
1930
  label: () => {
1958
1931
  var _a, _b, _c;
1959
- const variant = this._resumeVariant();
1932
+ const variant = this._resolvedVariant();
1960
1933
  const verb = (_c = (_b = (_a = this._hooks).resumeLabel) === null || _b === void 0 ? void 0 : _b.call(_a, active())) !== null && _c !== void 0 ? _c : 'Resume';
1961
1934
  return variant ? `${verb} (${variant.label})` : verb;
1962
1935
  },
1963
- icon: () => { var _a; return (((_a = this._resumeVariant()) === null || _a === void 0 ? void 0 : _a.unsafe) ? shieldIcon : undefined); },
1936
+ icon: () => { var _a; return (((_a = this._resolvedVariant()) === null || _a === void 0 ? void 0 : _a.unsafe) ? shieldIcon : undefined); },
1964
1937
  execute: () => {
1965
1938
  const s = active();
1966
1939
  if (s) {
@@ -1970,15 +1943,13 @@ export class AssistantSessionsPanel extends Widget {
1970
1943
  });
1971
1944
  for (const mode of this._variantModes) {
1972
1945
  this._commands.addCommand(this._cmd(`resume-${mode.id}`), {
1973
- label: `Resume (${mode.menuLabel})`,
1946
+ // The same verb as the plain item, so a row a live worker holds says
1947
+ // here too that the worker is stopped first.
1948
+ label: () => { var _a, _b, _c; return `${(_c = (_b = (_a = this._hooks).resumeLabel) === null || _b === void 0 ? void 0 : _b.call(_a, active())) !== null && _c !== void 0 ? _c : 'Resume'} (${mode.menuLabel})`; },
1974
1949
  icon: shieldIcon,
1975
1950
  // Absent while the plain item already resolves to this same mode - it
1976
1951
  // would be a second entry building an identical launch.
1977
1952
  isVisible: () => !this._buildsSameLaunch(mode),
1978
- // A conversation a live worker already holds was started in whatever
1979
- // mode that worker has; joining it cannot change that, so the variant
1980
- // is disabled rather than silently joining in the wrong mode.
1981
- isEnabled: () => { var _a; return !(this._descriptor.hasBgAgents && ((_a = active()) === null || _a === void 0 ? void 0 : _a.bg_id)); },
1982
1953
  execute: () => {
1983
1954
  const s = active();
1984
1955
  if (s) {
@@ -2127,13 +2098,11 @@ export class AssistantSessionsPanel extends Widget {
2127
2098
  this._commands.addCommand(this._cmd('open-branch'), {
2128
2099
  // Names the mode the launch carries, like Resume, `+` and Branch Session
2129
2100
  // do - this item opens a terminal, so the mode in force applies to it and
2130
- // this was the one launch surface that stayed silent about it. Attach-
2131
- // aware per conversation: the submenu passes `bg`, since a branch a live
2132
- // worker holds is attached to and takes no mode (DEF-46's rule).
2101
+ // this was the one launch surface that stayed silent about it.
2133
2102
  label: args => {
2134
2103
  var _a;
2135
2104
  const base = String((_a = args.label) !== null && _a !== void 0 ? _a : '');
2136
- const variant = this._resumeVariant(args.bg ? { bg_id: String(args.bg) } : null);
2105
+ const variant = this._resolvedVariant();
2137
2106
  return variant ? `${base} (${variant.label})` : base;
2138
2107
  },
2139
2108
  // Intent glyph, not row state - see switch-branch above.
@@ -2436,10 +2405,7 @@ export class AssistantSessionsPanel extends Widget {
2436
2405
  command: this._cmd('open-branch'),
2437
2406
  args: {
2438
2407
  session_id: b.session_id,
2439
- label: this._branchMenuLabel(b),
2440
- // The label reads this to tell an attach from a launch; the
2441
- // provider's own worker capability is tested where it is answered.
2442
- ...(b.bg_id ? { bg: b.bg_id } : {})
2408
+ label: this._branchMenuLabel(b)
2443
2409
  }
2444
2410
  });
2445
2411
  }
@@ -2463,7 +2429,7 @@ export class AssistantSessionsPanel extends Widget {
2463
2429
  branchName: b => this._branchDisplayName(b),
2464
2430
  formatTime: ms => this._formatRelativeTime(ms),
2465
2431
  branchBadge: b => this._bgBadgeFor(b),
2466
- openTitle: id => this._openBranchTitle(session, id),
2432
+ openTitle: () => this._openBranchTitle(session),
2467
2433
  onSwitch: id => void this._switchBranch(session, id),
2468
2434
  onOpen: id => void this._openSession(session, undefined, id),
2469
2435
  onDelete: ids => this._deleteBranches(session, ids),
@@ -2475,21 +2441,16 @@ export class AssistantSessionsPanel extends Widget {
2475
2441
  }
2476
2442
  /** Tooltip for one Open button in the Manage Sessions popup. Same rule as the
2477
2443
  * Open Branched Conversation submenu: the button launches a terminal, so it
2478
- * names the mode that launch carries, and a conversation a live worker holds
2479
- * is attached to and carries none. The row's own conversation is not in
2480
- * `_lastBranches`, so it answers from the session. */
2481
- _openBranchTitle(session, sessionId) {
2482
- var _a, _b, _c, _d;
2444
+ * names the mode that launch carries. */
2445
+ _openBranchTitle(session) {
2446
+ var _a, _b, _c;
2483
2447
  // One terminal serves a project-scoped assistant, so the button focuses
2484
2448
  // or starts it, under the provider's own verb for that.
2485
2449
  if (this._descriptor.terminalScope === 'project') {
2486
2450
  return ((_c = (_b = (_a = this._hooks).resumeLabel) === null || _b === void 0 ? void 0 : _b.call(_a, session)) !== null && _c !== void 0 ? _c : "Open the project's terminal");
2487
2451
  }
2488
2452
  const base = 'Open this conversation in its own terminal';
2489
- const target = sessionId === session.session_id
2490
- ? session
2491
- : ((_d = this._lastBranches.find(b => b.session_id === sessionId)) !== null && _d !== void 0 ? _d : null);
2492
- const variant = this._resumeVariant(target);
2453
+ const variant = this._resolvedVariant();
2493
2454
  return variant ? `${base} (${variant.label})` : base;
2494
2455
  }
2495
2456
  /** The live-worker chip for a branch row, when the provider has workers at
@@ -309,7 +309,8 @@ export interface ITabColourEvents {
309
309
  * shared panel. */
310
310
  export interface IProviderHooks {
311
311
  /** Label for the primary open action, when the provider distinguishes verbs
312
- * (joining a live worker is not the same as resuming a dormant store entry). */
312
+ * (stopping a live worker before resuming is not the same as resuming a
313
+ * dormant store entry). */
313
314
  resumeLabel?: (session: ISession | null) => string;
314
315
  /** Extra lines for the row tooltip. */
315
316
  tooltipLines?: (session: ISession) => string[];
@@ -70,7 +70,7 @@ export const descriptor = {
70
70
  legacyPluginId: 'jupyterlab_claude_code_extension'
71
71
  };
72
72
  export const hooks = {
73
- // Joining a live agent and resuming a dormant conversation are different
74
- // verbs, so the item says which one this row gets before the click.
75
- resumeLabel: (session) => (session === null || session === void 0 ? void 0 : session.bg_id) ? 'Attach to Background Agent' : 'Resume'
73
+ // Opening a conversation a live agent holds stops the agent first, so the
74
+ // item says so before the click (DEF-PROV-272).
75
+ resumeLabel: (session) => (session === null || session === void 0 ? void 0 : session.bg_id) ? 'Stop Background Agent and Resume' : 'Resume'
76
76
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab_ai_code_assistants_extension",
3
- "version": "1.2.35",
3
+ "version": "1.2.38",
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",
@@ -645,13 +645,15 @@ describe('DEF-46 - the row tooltip names only a mode the row launches with', ()
645
645
  expect(tooltip([session()])).toContain('Launch mode: Skip Permissions');
646
646
  });
647
647
 
648
- it('says nothing about a mode on a row a live worker already holds', () => {
648
+ it('names the mode on a row a live worker holds, which is resumed too', () => {
649
649
  panel.setModes({ skip: true });
650
- // An attach is issued before the flag is appended, deliberately, so the
651
- // tooltip would otherwise promise something the assistant never receives.
650
+ // The click stops the worker and resumes the conversation, and a resume
651
+ // carries the mode (DEF-PROV-272).
652
652
  const text = tooltip([session({ bg_id: 'abcd1234' })]);
653
- expect(text).toContain('Background agent: abcd1234 (click attaches to it)');
654
- expect(text).not.toContain('Launch mode');
653
+ expect(text).toContain(
654
+ 'Background agent: abcd1234 (click stops it and resumes here)'
655
+ );
656
+ expect(text).toContain('Launch mode: Skip Permissions');
655
657
  });
656
658
  });
657
659
 
package/src/core/panel.ts CHANGED
@@ -1899,9 +1899,9 @@ export class AssistantSessionsPanel extends Widget {
1899
1899
  );
1900
1900
  name.appendChild(badge);
1901
1901
  }
1902
- // A live worker owns this conversation, so a click joins it rather than
1903
- // resuming a dormant one. Marked so the difference is visible before the
1904
- // click, not a surprise after it. No own title: a child title shadows the
1902
+ // A live worker owns this conversation, so a click stops it before
1903
+ // resuming. Marked so the difference is visible before the click, not a
1904
+ // surprise after it. No own title: a child title shadows the
1905
1905
  // row's, which would hide the path behind a two-character chip.
1906
1906
  if (this._descriptor.hasBgAgents && session.bg_id) {
1907
1907
  const bg = document.createElement('span');
@@ -2086,15 +2086,12 @@ export class AssistantSessionsPanel extends Widget {
2086
2086
  lines.push('Status: active');
2087
2087
  }
2088
2088
  if (this._descriptor.hasBgAgents && s.bg_id) {
2089
- lines.push(`Background agent: ${s.bg_id} (click attaches to it)`);
2089
+ lines.push(
2090
+ `Background agent: ${s.bg_id} (click stops it and resumes here)`
2091
+ );
2090
2092
  }
2091
2093
  lines.push(...(this._hooks.tooltipLines?.(s) ?? []));
2092
- // What THIS row's click resolves to, which is the attach-aware question:
2093
- // the line above may have just said the click joins a live worker, and an
2094
- // attach is issued before the mode is appended, so `_resolvedVariant`
2095
- // promised a flag the server discards - one tooltip naming both, a line
2096
- // apart (DEF-46).
2097
- const variant = this._resumeVariant(s);
2094
+ const variant = this._resolvedVariant();
2098
2095
  if (variant) {
2099
2096
  // The row click launches with this, and the menu is the only other place
2100
2097
  // that says so.
@@ -2230,30 +2227,6 @@ export class AssistantSessionsPanel extends Widget {
2230
2227
  return resolved;
2231
2228
  }
2232
2229
 
2233
- /** The same mode as it applies to RESUME - of the active row by default, or
2234
- * of whichever conversation is named.
2235
- *
2236
- * An attach is not a launch: the server returns the argv before the flag is
2237
- * appended, deliberately, so promising the mode on a conversation a live
2238
- * worker already holds would name something the assistant never receives.
2239
- * That is true of resuming and of nothing else, so it is tested HERE rather
2240
- * than in `_resolvedVariant` - which is read by the `+` button and both
2241
- * new-session and branch items, none of which attach. Testing it there let
2242
- * one right-click on a background-agent row strip the mode's name off every
2243
- * one of those surfaces panel-wide, while their launches went on carrying it
2244
- * (DEF-37).
2245
- *
2246
- * The argument is what lets a surface that describes SOMEONE ELSE'S resume -
2247
- * a row tooltip built during a render, a branch item in the submenu - ask the
2248
- * question about that conversation rather than about the active row.
2249
- */
2250
- private _resumeVariant(
2251
- target: { bg_id?: string | null } | null = this._activeSession
2252
- ): IResolvedLaunchMode | null {
2253
- const joining = this._descriptor.hasBgAgents && !!target?.bg_id;
2254
- return joining ? null : this._resolvedVariant();
2255
- }
2256
-
2257
2230
  /** The variant suffix a plain item wears while that mode is in force. */
2258
2231
  private _variantSuffix(): string {
2259
2232
  const resolved = this._resolvedVariant();
@@ -2302,15 +2275,14 @@ export class AssistantSessionsPanel extends Widget {
2302
2275
  });
2303
2276
 
2304
2277
  this._commands.addCommand(this._cmd('resume'), {
2305
- // Joining a live worker and resuming a dormant conversation are
2306
- // different verbs, so a provider that has both says which one this row
2307
- // gets.
2278
+ // Resuming a conversation a live worker holds stops that worker first,
2279
+ // so a provider that has workers says which of the two this row gets.
2308
2280
  label: () => {
2309
- const variant = this._resumeVariant();
2281
+ const variant = this._resolvedVariant();
2310
2282
  const verb = this._hooks.resumeLabel?.(active()) ?? 'Resume';
2311
2283
  return variant ? `${verb} (${variant.label})` : verb;
2312
2284
  },
2313
- icon: () => (this._resumeVariant()?.unsafe ? shieldIcon : undefined),
2285
+ icon: () => (this._resolvedVariant()?.unsafe ? shieldIcon : undefined),
2314
2286
  execute: () => {
2315
2287
  const s = active();
2316
2288
  if (s) {
@@ -2321,15 +2293,14 @@ export class AssistantSessionsPanel extends Widget {
2321
2293
 
2322
2294
  for (const mode of this._variantModes) {
2323
2295
  this._commands.addCommand(this._cmd(`resume-${mode.id}`), {
2324
- label: `Resume (${mode.menuLabel})`,
2296
+ // The same verb as the plain item, so a row a live worker holds says
2297
+ // here too that the worker is stopped first.
2298
+ label: () =>
2299
+ `${this._hooks.resumeLabel?.(active()) ?? 'Resume'} (${mode.menuLabel})`,
2325
2300
  icon: shieldIcon,
2326
2301
  // Absent while the plain item already resolves to this same mode - it
2327
2302
  // would be a second entry building an identical launch.
2328
2303
  isVisible: () => !this._buildsSameLaunch(mode),
2329
- // A conversation a live worker already holds was started in whatever
2330
- // mode that worker has; joining it cannot change that, so the variant
2331
- // is disabled rather than silently joining in the wrong mode.
2332
- isEnabled: () => !(this._descriptor.hasBgAgents && active()?.bg_id),
2333
2304
  execute: () => {
2334
2305
  const s = active();
2335
2306
  if (s) {
@@ -2502,14 +2473,10 @@ export class AssistantSessionsPanel extends Widget {
2502
2473
  this._commands.addCommand(this._cmd('open-branch'), {
2503
2474
  // Names the mode the launch carries, like Resume, `+` and Branch Session
2504
2475
  // do - this item opens a terminal, so the mode in force applies to it and
2505
- // this was the one launch surface that stayed silent about it. Attach-
2506
- // aware per conversation: the submenu passes `bg`, since a branch a live
2507
- // worker holds is attached to and takes no mode (DEF-46's rule).
2476
+ // this was the one launch surface that stayed silent about it.
2508
2477
  label: args => {
2509
2478
  const base = String(args.label ?? '');
2510
- const variant = this._resumeVariant(
2511
- args.bg ? { bg_id: String(args.bg) } : null
2512
- );
2479
+ const variant = this._resolvedVariant();
2513
2480
  return variant ? `${base} (${variant.label})` : base;
2514
2481
  },
2515
2482
  // Intent glyph, not row state - see switch-branch above.
@@ -2844,10 +2811,7 @@ export class AssistantSessionsPanel extends Widget {
2844
2811
  command: this._cmd('open-branch'),
2845
2812
  args: {
2846
2813
  session_id: b.session_id,
2847
- label: this._branchMenuLabel(b),
2848
- // The label reads this to tell an attach from a launch; the
2849
- // provider's own worker capability is tested where it is answered.
2850
- ...(b.bg_id ? { bg: b.bg_id } : {})
2814
+ label: this._branchMenuLabel(b)
2851
2815
  }
2852
2816
  });
2853
2817
  }
@@ -2875,7 +2839,7 @@ export class AssistantSessionsPanel extends Widget {
2875
2839
  branchName: b => this._branchDisplayName(b),
2876
2840
  formatTime: ms => this._formatRelativeTime(ms),
2877
2841
  branchBadge: b => this._bgBadgeFor(b),
2878
- openTitle: id => this._openBranchTitle(session, id),
2842
+ openTitle: () => this._openBranchTitle(session),
2879
2843
  onSwitch: id => void this._switchBranch(session, id),
2880
2844
  onOpen: id => void this._openSession(session, undefined, id),
2881
2845
  onDelete: ids => this._deleteBranches(session, ids),
@@ -2888,10 +2852,8 @@ export class AssistantSessionsPanel extends Widget {
2888
2852
 
2889
2853
  /** Tooltip for one Open button in the Manage Sessions popup. Same rule as the
2890
2854
  * Open Branched Conversation submenu: the button launches a terminal, so it
2891
- * names the mode that launch carries, and a conversation a live worker holds
2892
- * is attached to and carries none. The row's own conversation is not in
2893
- * `_lastBranches`, so it answers from the session. */
2894
- private _openBranchTitle(session: ISession, sessionId: string): string {
2855
+ * names the mode that launch carries. */
2856
+ private _openBranchTitle(session: ISession): string {
2895
2857
  // One terminal serves a project-scoped assistant, so the button focuses
2896
2858
  // or starts it, under the provider's own verb for that.
2897
2859
  if (this._descriptor.terminalScope === 'project') {
@@ -2900,11 +2862,7 @@ export class AssistantSessionsPanel extends Widget {
2900
2862
  );
2901
2863
  }
2902
2864
  const base = 'Open this conversation in its own terminal';
2903
- const target =
2904
- sessionId === session.session_id
2905
- ? session
2906
- : (this._lastBranches.find(b => b.session_id === sessionId) ?? null);
2907
- const variant = this._resumeVariant(target);
2865
+ const variant = this._resolvedVariant();
2908
2866
  return variant ? `${base} (${variant.label})` : base;
2909
2867
  }
2910
2868
 
package/src/core/types.ts CHANGED
@@ -349,7 +349,8 @@ export interface ITabColourEvents {
349
349
  * shared panel. */
350
350
  export interface IProviderHooks {
351
351
  /** Label for the primary open action, when the provider distinguishes verbs
352
- * (joining a live worker is not the same as resuming a dormant store entry). */
352
+ * (stopping a live worker before resuming is not the same as resuming a
353
+ * dormant store entry). */
353
354
  resumeLabel?: (session: ISession | null) => string;
354
355
  /** Extra lines for the row tooltip. */
355
356
  tooltipLines?: (session: ISession) => string[];
@@ -76,8 +76,8 @@ export const descriptor: IProviderDescriptor = {
76
76
  };
77
77
 
78
78
  export const hooks: IProviderHooks = {
79
- // Joining a live agent and resuming a dormant conversation are different
80
- // verbs, so the item says which one this row gets before the click.
79
+ // Opening a conversation a live agent holds stops the agent first, so the
80
+ // item says so before the click (DEF-PROV-272).
81
81
  resumeLabel: (session: ISession | null) =>
82
- session?.bg_id ? 'Attach to Background Agent' : 'Resume'
82
+ session?.bg_id ? 'Stop Background Agent and Resume' : 'Resume'
83
83
  };