jupyterlab_ai_code_assistants_extension 1.0.10 → 1.0.17
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/core/limits.d.ts +12 -0
- package/lib/core/limits.js +12 -0
- package/lib/core/panel.d.ts +4 -7
- package/lib/core/panel.js +25 -13
- package/lib/core/types.d.ts +4 -9
- package/lib/index.js +2 -1
- package/lib/providers/claude.js +1 -1
- package/lib/providers/codex.js +1 -1
- package/lib/providers/gemini.js +4 -4
- package/lib/providers/kimi.js +3 -3
- package/package.json +1 -1
- package/src/__tests__/core-neutrality.spec.ts +16 -1
- package/src/__tests__/panel.spec.ts +1 -1
- package/src/__tests__/registry.spec.ts +2 -3
- package/src/core/limits.ts +12 -0
- package/src/core/panel.ts +44 -13
- package/src/core/types.ts +4 -10
- package/src/index.ts +1 -1
- package/src/providers/claude.ts +1 -1
- package/src/providers/codex.ts +1 -1
- package/src/providers/gemini.ts +4 -4
- package/src/providers/kimi.ts +3 -3
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The ONE home for the Recent section's size bounds. `src/core/panel.ts`
|
|
2
|
+
* clamps with them, `src/index.ts` defaults with them, and
|
|
3
|
+
* `scripts/generate-schema.mjs` writes them into `schema/plugin.json`, so a
|
|
4
|
+
* change here is the only change needed.
|
|
5
|
+
*
|
|
6
|
+
* Keep this module import-free. The schema generator loads the compiled
|
|
7
|
+
* `lib/core/limits.js` under plain Node, exactly as it loads the provider
|
|
8
|
+
* barrel; a single browser-only import would break that.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_RECENT_LIMIT = 10;
|
|
11
|
+
export declare const MIN_RECENT_LIMIT = 1;
|
|
12
|
+
export declare const MAX_RECENT_LIMIT = 100;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The ONE home for the Recent section's size bounds. `src/core/panel.ts`
|
|
2
|
+
* clamps with them, `src/index.ts` defaults with them, and
|
|
3
|
+
* `scripts/generate-schema.mjs` writes them into `schema/plugin.json`, so a
|
|
4
|
+
* change here is the only change needed.
|
|
5
|
+
*
|
|
6
|
+
* Keep this module import-free. The schema generator loads the compiled
|
|
7
|
+
* `lib/core/limits.js` under plain Node, exactly as it loads the provider
|
|
8
|
+
* barrel; a single browser-only import would break that.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_RECENT_LIMIT = 10;
|
|
11
|
+
export const MIN_RECENT_LIMIT = 1;
|
|
12
|
+
export const MAX_RECENT_LIMIT = 100;
|
package/lib/core/panel.d.ts
CHANGED
|
@@ -5,13 +5,6 @@ import { IColourfulTabs } from 'jupyterlab_colourful_tab_extension';
|
|
|
5
5
|
import { Message } from '@lumino/messaging';
|
|
6
6
|
import { Widget } from '@lumino/widgets';
|
|
7
7
|
import { IProviderDescriptor, IProviderHooks } from './types';
|
|
8
|
-
/** Default and bounds for the Recent section's size. Exported because
|
|
9
|
-
* `src/index.ts` reads the same setting and used to restate all three as
|
|
10
|
-
* literals; `schema/plugin.json` declares the identical default, minimum and
|
|
11
|
-
* maximum, so a change here is a change there. */
|
|
12
|
-
export declare const DEFAULT_RECENT_LIMIT = 10;
|
|
13
|
-
export declare const MIN_RECENT_LIMIT = 1;
|
|
14
|
-
export declare const MAX_RECENT_LIMIT = 100;
|
|
15
8
|
export type PresentationMode = 'name' | 'path';
|
|
16
9
|
export declare const DEFAULT_PRESENTATION_MODE: PresentationMode;
|
|
17
10
|
/** Widget id for one provider's panel - what the layout restorer keys on. */
|
|
@@ -68,6 +61,10 @@ export declare class AssistantSessionsPanel extends Widget {
|
|
|
68
61
|
/** Inline, per-panel error. One provider's unreachable server must leave the
|
|
69
62
|
* other panels alone, so nothing here is modal. */
|
|
70
63
|
private _setInlineError;
|
|
64
|
+
/** A polled or refreshed fetch failed. Only these two paths own a retry, so
|
|
65
|
+
* only they may promise one. DEF-81: a client-side timeout means the server
|
|
66
|
+
* was REACHED and did not answer in time, which is a different thing to
|
|
67
|
+
* report than a server that is gone. */
|
|
71
68
|
private _showError;
|
|
72
69
|
/** A one-shot action failed. Nothing retries it, so the copy names what did
|
|
73
70
|
* not happen instead of promising a retry the poll loop alone performs. */
|
package/lib/core/panel.js
CHANGED
|
@@ -5,10 +5,11 @@ import { UUID } from '@lumino/coreutils';
|
|
|
5
5
|
import { Widget } from '@lumino/widgets';
|
|
6
6
|
import { ColourStore } from './colour';
|
|
7
7
|
import { addIcon, branchIcon, filterIcon, providerIcon, refreshIcon, removeIcon, starFilledIcon, switchIcon, warningIcon } from './icons';
|
|
8
|
+
import { DEFAULT_RECENT_LIMIT, MAX_RECENT_LIMIT, MIN_RECENT_LIMIT } from './limits';
|
|
8
9
|
import { resolveLaunchMode, resolvedLaunchModeEntry } from './modes';
|
|
9
10
|
import { showManageSessionsPopup } from './popup';
|
|
10
11
|
import { LOG_PREFIX } from './registry';
|
|
11
|
-
import { isResponseStatus, requestProvider, withQuery } from './request';
|
|
12
|
+
import { isRequestTimeout, isResponseStatus, requestProvider, withQuery } from './request';
|
|
12
13
|
import { TerminalManager } from './terminals';
|
|
13
14
|
// Rows refresh on this cadence. It is also what refreshes whatever roster the
|
|
14
15
|
// server caches for the context menu, so raising it past the server's cache
|
|
@@ -33,13 +34,6 @@ const REFRESH_SPIN_FLOOR_MS = 500;
|
|
|
33
34
|
// faster cadence (bounded) so a new branch surfaces in seconds.
|
|
34
35
|
const BRANCH_WATCH_INTERVAL_MS = 2000;
|
|
35
36
|
const BRANCH_WATCH_MAX_ATTEMPTS = 90; // ~3 minutes
|
|
36
|
-
/** Default and bounds for the Recent section's size. Exported because
|
|
37
|
-
* `src/index.ts` reads the same setting and used to restate all three as
|
|
38
|
-
* literals; `schema/plugin.json` declares the identical default, minimum and
|
|
39
|
-
* maximum, so a change here is a change there. */
|
|
40
|
-
export const DEFAULT_RECENT_LIMIT = 10;
|
|
41
|
-
export const MIN_RECENT_LIMIT = 1;
|
|
42
|
-
export const MAX_RECENT_LIMIT = 100;
|
|
43
37
|
/** Conversations listed inline in a branch submenu before the popup takes over. */
|
|
44
38
|
const INLINE_BRANCH_LIMIT = 5;
|
|
45
39
|
export const DEFAULT_PRESENTATION_MODE = 'name';
|
|
@@ -528,10 +522,17 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
528
522
|
this._errorEl.textContent = message !== null && message !== void 0 ? message : '';
|
|
529
523
|
this._errorEl.hidden = !message;
|
|
530
524
|
}
|
|
525
|
+
/** A polled or refreshed fetch failed. Only these two paths own a retry, so
|
|
526
|
+
* only they may promise one. DEF-81: a client-side timeout means the server
|
|
527
|
+
* was REACHED and did not answer in time, which is a different thing to
|
|
528
|
+
* report than a server that is gone. */
|
|
531
529
|
_showError(err) {
|
|
532
530
|
const message = err instanceof Error ? err.message : String(err);
|
|
533
531
|
this._logError(message);
|
|
534
|
-
|
|
532
|
+
const copy = isRequestTimeout(err)
|
|
533
|
+
? 'The server is not answering - retrying.'
|
|
534
|
+
: 'Could not reach the server - retrying.';
|
|
535
|
+
this._setInlineError(`${copy} ${message}`);
|
|
535
536
|
}
|
|
536
537
|
/** A one-shot action failed. Nothing retries it, so the copy names what did
|
|
537
538
|
* not happen instead of promising a retry the poll loop alone performs. */
|
|
@@ -704,7 +705,6 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
704
705
|
bar.remove();
|
|
705
706
|
message.classList.add('jp-AiAssistantsPanel-cleanupError');
|
|
706
707
|
message.textContent = `Cleanup failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
707
|
-
this._showError(err);
|
|
708
708
|
}
|
|
709
709
|
}
|
|
710
710
|
// ---------------------------------------------------------------- launch
|
|
@@ -790,7 +790,7 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
790
790
|
}
|
|
791
791
|
const parentColour = this._terminals.colourFor(session);
|
|
792
792
|
let name;
|
|
793
|
-
if (this._descriptor.
|
|
793
|
+
if (this._descriptor.promptsForBranchName) {
|
|
794
794
|
const named = await InputDialog.getText({
|
|
795
795
|
title: 'Branch Session',
|
|
796
796
|
label: 'Name for the new session',
|
|
@@ -872,6 +872,18 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
872
872
|
* answers with the new id, so the branch exists before the terminal opens
|
|
873
873
|
* and one refresh surfaces it - no watcher needed. */
|
|
874
874
|
async _branchByServerCopy(session, name, force) {
|
|
875
|
+
// Let the parent's colour settle before the server reads it (DEF-74). The
|
|
876
|
+
// server copies the store entry while minting the fork, so a colour picked
|
|
877
|
+
// on the parent's tab moments earlier - still queued in this store's
|
|
878
|
+
// request chain, unsent - is not in the server's store yet and the branch
|
|
879
|
+
// is born wearing the PREVIOUS tint. Same gate as `_inheritColour`
|
|
880
|
+
// (DEF-31): a reload is a queued request like every other of this store's
|
|
881
|
+
// (DEF-30), so it is not sent until the capture ahead of it has been
|
|
882
|
+
// answered, and awaiting it is what makes the server's read fresh. Only
|
|
883
|
+
// inside the capture window, so an ordinary fork still costs no request.
|
|
884
|
+
if (this._colours.isPending(session.session_id)) {
|
|
885
|
+
await this._colours.load();
|
|
886
|
+
}
|
|
875
887
|
try {
|
|
876
888
|
const fork = await requestProvider(this._descriptor.id, 'branch', this._serverSettings, {
|
|
877
889
|
method: 'POST',
|
|
@@ -1761,7 +1773,7 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
1761
1773
|
}
|
|
1762
1774
|
this._app.commands
|
|
1763
1775
|
.execute('terminal:create-new', { cwd: rel })
|
|
1764
|
-
.catch(err => this.
|
|
1776
|
+
.catch(err => this._showActionError('Could not open a terminal for this session - try again.', err));
|
|
1765
1777
|
}
|
|
1766
1778
|
});
|
|
1767
1779
|
this._commands.addCommand(this._cmd('show-in-filebrowser'), {
|
|
@@ -1779,7 +1791,7 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
1779
1791
|
}
|
|
1780
1792
|
this._app.commands
|
|
1781
1793
|
.execute('filebrowser:go-to-path', { path: rel })
|
|
1782
|
-
.catch(err => this.
|
|
1794
|
+
.catch(err => this._showActionError('Could not open the project folder - try again.', err));
|
|
1783
1795
|
}
|
|
1784
1796
|
});
|
|
1785
1797
|
this._commands.addCommand(this._cmd('copy-path'), {
|
package/lib/core/types.d.ts
CHANGED
|
@@ -18,13 +18,6 @@ export type ForkStrategy = 'native-flag' | 'native-command' | 'server-copy';
|
|
|
18
18
|
/** Where a conversation's DEFAULT tab tint comes from. A user-set colour in
|
|
19
19
|
* the extension's own write-back store always wins over all three. */
|
|
20
20
|
export type ColourSource = 'native' | 'derived' | 'none';
|
|
21
|
-
/** Who owns a branched conversation's name.
|
|
22
|
-
*
|
|
23
|
-
* - `launch-flag` - the CLI is told the name at launch and stamps it itself
|
|
24
|
-
* - `server-side` - the server writes the name into the copied store entry
|
|
25
|
-
* - `none` - the assistant has no naming surface, so no name is asked for
|
|
26
|
-
*/
|
|
27
|
-
export type NamingStrategy = 'launch-flag' | 'server-side' | 'none';
|
|
28
21
|
/** One launch-behaviour setting of an assistant, in that assistant's own
|
|
29
22
|
* terminology. `boolean` modes are the unsafe/skip-approval switches: they get
|
|
30
23
|
* a settings toggle (off by default) AND a context-menu launch variant.
|
|
@@ -76,7 +69,9 @@ export interface IProviderDescriptor {
|
|
|
76
69
|
cliBinary: string;
|
|
77
70
|
forkStrategy: ForkStrategy;
|
|
78
71
|
colourSource: ColourSource;
|
|
79
|
-
|
|
72
|
+
/** Whether the branch flow asks the user to name the new conversation.
|
|
73
|
+
* Where that name then GOES is `forkStrategy`'s job. */
|
|
74
|
+
promptsForBranchName: boolean;
|
|
80
75
|
/** The CLI starts a brand-new conversation under an id we give it, so a
|
|
81
76
|
* fresh session is identifiable from its argv on the first poll instead of
|
|
82
77
|
* being an unknown terminal until it writes its store entry. Independent of
|
|
@@ -211,7 +206,7 @@ export interface ILaunchRequest {
|
|
|
211
206
|
fork_session_id?: string;
|
|
212
207
|
/** Parent conversation to fork from (`forkStrategy: 'native-command'`). */
|
|
213
208
|
fork_from?: string;
|
|
214
|
-
/** Name to stamp on the new conversation (`
|
|
209
|
+
/** Name to stamp on the new conversation (`promptsForBranchName: true`). */
|
|
215
210
|
name?: string;
|
|
216
211
|
/** The launch mode this action runs under, as one token in the assistant's
|
|
217
212
|
* own terminology: a boolean mode is its bare `ILaunchMode.id`, an enum mode
|
package/lib/index.js
CHANGED
|
@@ -4,7 +4,8 @@ import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
|
|
|
4
4
|
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
5
5
|
import { ITerminalTracker } from '@jupyterlab/terminal';
|
|
6
6
|
import { IColourfulTabs } from 'jupyterlab_colourful_tab_extension';
|
|
7
|
-
import {
|
|
7
|
+
import { DEFAULT_RECENT_LIMIT } from './core/limits';
|
|
8
|
+
import { AssistantSessionsPanel, DEFAULT_PRESENTATION_MODE, commandId, panelWidgetId } from './core/panel';
|
|
8
9
|
import { LOG_PREFIX, ProviderRegistry } from './core/registry';
|
|
9
10
|
import { requestAPI } from './core/request';
|
|
10
11
|
import { PROVIDERS } from './providers';
|
package/lib/providers/claude.js
CHANGED
|
@@ -45,7 +45,7 @@ export const descriptor = {
|
|
|
45
45
|
// `-n <name>` at launch: Claude stamps the name itself and re-stamps it on
|
|
46
46
|
// every turn, which is the only way a fork's name survives - a title written
|
|
47
47
|
// after the fact loses to the parent title the fork inherits.
|
|
48
|
-
|
|
48
|
+
promptsForBranchName: true,
|
|
49
49
|
mintsNewSessionId: true,
|
|
50
50
|
launchModes: [
|
|
51
51
|
{
|
package/lib/providers/codex.js
CHANGED
|
@@ -40,7 +40,7 @@ export const descriptor = {
|
|
|
40
40
|
colourSource: 'none',
|
|
41
41
|
// Forks are unnamed - Codex has no naming flag and no writable title field,
|
|
42
42
|
// so the panel never asks for a name it could not stamp anywhere.
|
|
43
|
-
|
|
43
|
+
promptsForBranchName: false,
|
|
44
44
|
// A bare `codex` mints its own thread id; there is no `--session-id`.
|
|
45
45
|
mintsNewSessionId: false,
|
|
46
46
|
launchModes: [
|
package/lib/providers/gemini.js
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
// * `colourSource: 'none'` - no colour command, and nothing in the chat store
|
|
10
10
|
// records one, so a tab tint only ever comes from the extension's own
|
|
11
11
|
// write-back store
|
|
12
|
-
// * `
|
|
13
|
-
// chat file's `summary`,
|
|
14
|
-
// conversation under; there is no naming flag to pass it to
|
|
12
|
+
// * `promptsForBranchName: true` - the branch flow asks for a name, which the
|
|
13
|
+
// server stamps into the copied chat file's `summary`, the field the CLI
|
|
14
|
+
// itself displays a conversation under; there is no naming flag to pass it to
|
|
15
15
|
// * `mintsNewSessionId: true` - `--session-id <uuid>` starts a fresh
|
|
16
16
|
// conversation under an id we chose, so a new session is identifiable from
|
|
17
17
|
// its argv on the first poll instead of being an unknown terminal
|
|
@@ -46,7 +46,7 @@ export const descriptor = {
|
|
|
46
46
|
colourSource: 'none',
|
|
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
50
|
// `--session-id <uuid>` starts a conversation under an id we mint.
|
|
51
51
|
mintsNewSessionId: true,
|
|
52
52
|
launchModes: [
|
package/lib/providers/kimi.js
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// * `colourSource: 'derived'` - kimi has no `/color`, so the default tint is a
|
|
10
10
|
// stable hash of the conversation id (a user-set colour still wins, per the
|
|
11
11
|
// Colour section of the acceptance criteria)
|
|
12
|
-
// * `
|
|
13
|
-
// `state.json`, not passed to the CLI
|
|
12
|
+
// * `promptsForBranchName: true` - the branch flow asks for a name, which the
|
|
13
|
+
// server stamps into the copied `state.json`, not passed to the CLI
|
|
14
14
|
// * one launch mode, `--yolo`, under kimi's own name
|
|
15
15
|
//
|
|
16
16
|
// The only hook is the branch label: kimi's conversation titles are
|
|
@@ -112,7 +112,7 @@ export const descriptor = {
|
|
|
112
112
|
colourSource: 'derived',
|
|
113
113
|
// The fork's name is written into the copy's `state.json` by the server; the
|
|
114
114
|
// CLI has no naming flag to take it.
|
|
115
|
-
|
|
115
|
+
promptsForBranchName: true,
|
|
116
116
|
// A bare `kimi` mints its own id and writes it on the first turn, and `-S`
|
|
117
117
|
// only ever resumes - so a new conversation cannot be launched under an id
|
|
118
118
|
// 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.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "Jupyterlab extension to replace individual jupyterlab_*_code_extension and integrate into one, that offers joint capability, joint settings page and individual right toolbar panels per code assistant, with the ability in settings to decide which code assistant support is on (default - all are enabled)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -84,7 +84,12 @@ const CORE_DIR = path.resolve(__dirname, '..', 'core');
|
|
|
84
84
|
/** Drop block and line comments, leaving code and string literals. */
|
|
85
85
|
function stripComments(source: string): string {
|
|
86
86
|
return source
|
|
87
|
-
.replace(/\/\*[\s\S]*?\*\//g,
|
|
87
|
+
.replace(/\/\*[\s\S]*?\*\//g, match =>
|
|
88
|
+
// DEF-80: keep the comment's newlines, or every line after a block
|
|
89
|
+
// comment is reported at the wrong number - the licence header alone
|
|
90
|
+
// shifts a real violation by the length of the header.
|
|
91
|
+
'\n'.repeat((match.match(/\n/g) ?? []).length)
|
|
92
|
+
)
|
|
88
93
|
.replace(/^[^\n]*?\/\/.*$/gm, line =>
|
|
89
94
|
// Keep the part before a trailing `//`, so code on a commented line is
|
|
90
95
|
// still scanned - but drop a whole-line comment entirely.
|
|
@@ -200,4 +205,14 @@ describe('the comment-stripping the scan relies on', () => {
|
|
|
200
205
|
ASSISTANT_NAMES
|
|
201
206
|
);
|
|
202
207
|
});
|
|
208
|
+
|
|
209
|
+
it('reports a violation at its real line number, past a block comment', () => {
|
|
210
|
+
// DEF-80: the block comment used to be replaced by nothing, so every line
|
|
211
|
+
// after it was reported short by the comment's height - a violation under
|
|
212
|
+
// a header comment pointed at unrelated code.
|
|
213
|
+
const source = `/**\n * ported\n * from a comment\n */\nconst id = 'claude';`;
|
|
214
|
+
expect(offenders(source, ASSISTANT_NAMES)).toEqual([
|
|
215
|
+
[5, "const id = 'claude';"]
|
|
216
|
+
]);
|
|
217
|
+
});
|
|
203
218
|
});
|
|
@@ -17,7 +17,6 @@ const EXPECTED_IDS = ['claude', 'codex', 'kimi', 'gemini'];
|
|
|
17
17
|
|
|
18
18
|
const FORK_STRATEGIES = ['native-flag', 'native-command', 'server-copy'];
|
|
19
19
|
const COLOUR_SOURCES = ['native', 'derived', 'none'];
|
|
20
|
-
const NAMING_STRATEGIES = ['launch-flag', 'server-side', 'none'];
|
|
21
20
|
|
|
22
21
|
function stub(id: string): IProviderModule {
|
|
23
22
|
return {
|
|
@@ -30,7 +29,7 @@ function stub(id: string): IProviderModule {
|
|
|
30
29
|
cliBinary: id,
|
|
31
30
|
forkStrategy: 'server-copy',
|
|
32
31
|
colourSource: 'none',
|
|
33
|
-
|
|
32
|
+
promptsForBranchName: false,
|
|
34
33
|
mintsNewSessionId: false,
|
|
35
34
|
launchModes: [],
|
|
36
35
|
hasRemoteControl: false,
|
|
@@ -65,7 +64,7 @@ describe('the providers barrel', () => {
|
|
|
65
64
|
expect(descriptor.iconSvg).toContain('<svg');
|
|
66
65
|
expect(FORK_STRATEGIES).toContain(descriptor.forkStrategy);
|
|
67
66
|
expect(COLOUR_SOURCES).toContain(descriptor.colourSource);
|
|
68
|
-
expect(
|
|
67
|
+
expect(typeof descriptor.promptsForBranchName).toBe('boolean');
|
|
69
68
|
expect(typeof descriptor.mintsNewSessionId).toBe('boolean');
|
|
70
69
|
expect(typeof descriptor.hasRemoteControl).toBe('boolean');
|
|
71
70
|
expect(typeof descriptor.hasBgAgents).toBe('boolean');
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The ONE home for the Recent section's size bounds. `src/core/panel.ts`
|
|
2
|
+
* clamps with them, `src/index.ts` defaults with them, and
|
|
3
|
+
* `scripts/generate-schema.mjs` writes them into `schema/plugin.json`, so a
|
|
4
|
+
* change here is the only change needed.
|
|
5
|
+
*
|
|
6
|
+
* Keep this module import-free. The schema generator loads the compiled
|
|
7
|
+
* `lib/core/limits.js` under plain Node, exactly as it loads the provider
|
|
8
|
+
* barrel; a single browser-only import would break that.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_RECENT_LIMIT = 10;
|
|
11
|
+
export const MIN_RECENT_LIMIT = 1;
|
|
12
|
+
export const MAX_RECENT_LIMIT = 100;
|
package/src/core/panel.ts
CHANGED
|
@@ -34,6 +34,11 @@ import {
|
|
|
34
34
|
switchIcon,
|
|
35
35
|
warningIcon
|
|
36
36
|
} from './icons';
|
|
37
|
+
import {
|
|
38
|
+
DEFAULT_RECENT_LIMIT,
|
|
39
|
+
MAX_RECENT_LIMIT,
|
|
40
|
+
MIN_RECENT_LIMIT
|
|
41
|
+
} from './limits';
|
|
37
42
|
import {
|
|
38
43
|
IResolvedLaunchMode,
|
|
39
44
|
resolveLaunchMode,
|
|
@@ -41,7 +46,12 @@ import {
|
|
|
41
46
|
} from './modes';
|
|
42
47
|
import { showManageSessionsPopup } from './popup';
|
|
43
48
|
import { LOG_PREFIX } from './registry';
|
|
44
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
isRequestTimeout,
|
|
51
|
+
isResponseStatus,
|
|
52
|
+
requestProvider,
|
|
53
|
+
withQuery
|
|
54
|
+
} from './request';
|
|
45
55
|
import { TerminalManager } from './terminals';
|
|
46
56
|
import {
|
|
47
57
|
IBranch,
|
|
@@ -83,13 +93,6 @@ const REFRESH_SPIN_FLOOR_MS = 500;
|
|
|
83
93
|
// faster cadence (bounded) so a new branch surfaces in seconds.
|
|
84
94
|
const BRANCH_WATCH_INTERVAL_MS = 2_000;
|
|
85
95
|
const BRANCH_WATCH_MAX_ATTEMPTS = 90; // ~3 minutes
|
|
86
|
-
/** Default and bounds for the Recent section's size. Exported because
|
|
87
|
-
* `src/index.ts` reads the same setting and used to restate all three as
|
|
88
|
-
* literals; `schema/plugin.json` declares the identical default, minimum and
|
|
89
|
-
* maximum, so a change here is a change there. */
|
|
90
|
-
export const DEFAULT_RECENT_LIMIT = 10;
|
|
91
|
-
export const MIN_RECENT_LIMIT = 1;
|
|
92
|
-
export const MAX_RECENT_LIMIT = 100;
|
|
93
96
|
/** Conversations listed inline in a branch submenu before the popup takes over. */
|
|
94
97
|
const INLINE_BRANCH_LIMIT = 5;
|
|
95
98
|
|
|
@@ -601,10 +604,17 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
601
604
|
this._errorEl.hidden = !message;
|
|
602
605
|
}
|
|
603
606
|
|
|
607
|
+
/** A polled or refreshed fetch failed. Only these two paths own a retry, so
|
|
608
|
+
* only they may promise one. DEF-81: a client-side timeout means the server
|
|
609
|
+
* was REACHED and did not answer in time, which is a different thing to
|
|
610
|
+
* report than a server that is gone. */
|
|
604
611
|
private _showError(err: unknown): void {
|
|
605
612
|
const message = err instanceof Error ? err.message : String(err);
|
|
606
613
|
this._logError(message);
|
|
607
|
-
|
|
614
|
+
const copy = isRequestTimeout(err)
|
|
615
|
+
? 'The server is not answering - retrying.'
|
|
616
|
+
: 'Could not reach the server - retrying.';
|
|
617
|
+
this._setInlineError(`${copy} ${message}`);
|
|
608
618
|
}
|
|
609
619
|
|
|
610
620
|
/** A one-shot action failed. Nothing retries it, so the copy names what did
|
|
@@ -815,7 +825,6 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
815
825
|
message.textContent = `Cleanup failed: ${
|
|
816
826
|
err instanceof Error ? err.message : String(err)
|
|
817
827
|
}`;
|
|
818
|
-
this._showError(err);
|
|
819
828
|
}
|
|
820
829
|
}
|
|
821
830
|
|
|
@@ -916,7 +925,7 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
916
925
|
const parentColour = this._terminals.colourFor(session);
|
|
917
926
|
|
|
918
927
|
let name: string | undefined;
|
|
919
|
-
if (this._descriptor.
|
|
928
|
+
if (this._descriptor.promptsForBranchName) {
|
|
920
929
|
const named = await InputDialog.getText({
|
|
921
930
|
title: 'Branch Session',
|
|
922
931
|
label: 'Name for the new session',
|
|
@@ -1019,6 +1028,18 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
1019
1028
|
name: string | undefined,
|
|
1020
1029
|
force: string | undefined
|
|
1021
1030
|
): Promise<void> {
|
|
1031
|
+
// Let the parent's colour settle before the server reads it (DEF-74). The
|
|
1032
|
+
// server copies the store entry while minting the fork, so a colour picked
|
|
1033
|
+
// on the parent's tab moments earlier - still queued in this store's
|
|
1034
|
+
// request chain, unsent - is not in the server's store yet and the branch
|
|
1035
|
+
// is born wearing the PREVIOUS tint. Same gate as `_inheritColour`
|
|
1036
|
+
// (DEF-31): a reload is a queued request like every other of this store's
|
|
1037
|
+
// (DEF-30), so it is not sent until the capture ahead of it has been
|
|
1038
|
+
// answered, and awaiting it is what makes the server's read fresh. Only
|
|
1039
|
+
// inside the capture window, so an ordinary fork still costs no request.
|
|
1040
|
+
if (this._colours.isPending(session.session_id)) {
|
|
1041
|
+
await this._colours.load();
|
|
1042
|
+
}
|
|
1022
1043
|
try {
|
|
1023
1044
|
const fork = await requestProvider<IForkResponse>(
|
|
1024
1045
|
this._descriptor.id,
|
|
@@ -2065,7 +2086,12 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2065
2086
|
}
|
|
2066
2087
|
this._app.commands
|
|
2067
2088
|
.execute('terminal:create-new', { cwd: rel })
|
|
2068
|
-
.catch(err =>
|
|
2089
|
+
.catch(err =>
|
|
2090
|
+
this._showActionError(
|
|
2091
|
+
'Could not open a terminal for this session - try again.',
|
|
2092
|
+
err
|
|
2093
|
+
)
|
|
2094
|
+
);
|
|
2069
2095
|
}
|
|
2070
2096
|
});
|
|
2071
2097
|
|
|
@@ -2087,7 +2113,12 @@ export class AssistantSessionsPanel extends Widget {
|
|
|
2087
2113
|
}
|
|
2088
2114
|
this._app.commands
|
|
2089
2115
|
.execute('filebrowser:go-to-path', { path: rel })
|
|
2090
|
-
.catch(err =>
|
|
2116
|
+
.catch(err =>
|
|
2117
|
+
this._showActionError(
|
|
2118
|
+
'Could not open the project folder - try again.',
|
|
2119
|
+
err
|
|
2120
|
+
)
|
|
2121
|
+
);
|
|
2091
2122
|
}
|
|
2092
2123
|
});
|
|
2093
2124
|
|
package/src/core/types.ts
CHANGED
|
@@ -26,14 +26,6 @@ export type ForkStrategy = 'native-flag' | 'native-command' | 'server-copy';
|
|
|
26
26
|
* the extension's own write-back store always wins over all three. */
|
|
27
27
|
export type ColourSource = 'native' | 'derived' | 'none';
|
|
28
28
|
|
|
29
|
-
/** Who owns a branched conversation's name.
|
|
30
|
-
*
|
|
31
|
-
* - `launch-flag` - the CLI is told the name at launch and stamps it itself
|
|
32
|
-
* - `server-side` - the server writes the name into the copied store entry
|
|
33
|
-
* - `none` - the assistant has no naming surface, so no name is asked for
|
|
34
|
-
*/
|
|
35
|
-
export type NamingStrategy = 'launch-flag' | 'server-side' | 'none';
|
|
36
|
-
|
|
37
29
|
/** One launch-behaviour setting of an assistant, in that assistant's own
|
|
38
30
|
* terminology. `boolean` modes are the unsafe/skip-approval switches: they get
|
|
39
31
|
* a settings toggle (off by default) AND a context-menu launch variant.
|
|
@@ -86,7 +78,9 @@ export interface IProviderDescriptor {
|
|
|
86
78
|
cliBinary: string;
|
|
87
79
|
forkStrategy: ForkStrategy;
|
|
88
80
|
colourSource: ColourSource;
|
|
89
|
-
|
|
81
|
+
/** Whether the branch flow asks the user to name the new conversation.
|
|
82
|
+
* Where that name then GOES is `forkStrategy`'s job. */
|
|
83
|
+
promptsForBranchName: boolean;
|
|
90
84
|
/** The CLI starts a brand-new conversation under an id we give it, so a
|
|
91
85
|
* fresh session is identifiable from its argv on the first poll instead of
|
|
92
86
|
* being an unknown terminal until it writes its store entry. Independent of
|
|
@@ -235,7 +229,7 @@ export interface ILaunchRequest {
|
|
|
235
229
|
fork_session_id?: string;
|
|
236
230
|
/** Parent conversation to fork from (`forkStrategy: 'native-command'`). */
|
|
237
231
|
fork_from?: string;
|
|
238
|
-
/** Name to stamp on the new conversation (`
|
|
232
|
+
/** Name to stamp on the new conversation (`promptsForBranchName: true`). */
|
|
239
233
|
name?: string;
|
|
240
234
|
/** The launch mode this action runs under, as one token in the assistant's
|
|
241
235
|
* own terminology: a boolean mode is its bare `ILaunchMode.id`, an enum mode
|
package/src/index.ts
CHANGED
|
@@ -11,10 +11,10 @@ import { ITerminalTracker } from '@jupyterlab/terminal';
|
|
|
11
11
|
import { IColourfulTabs } from 'jupyterlab_colourful_tab_extension';
|
|
12
12
|
import { IDisposable } from '@lumino/disposable';
|
|
13
13
|
|
|
14
|
+
import { DEFAULT_RECENT_LIMIT } from './core/limits';
|
|
14
15
|
import {
|
|
15
16
|
AssistantSessionsPanel,
|
|
16
17
|
DEFAULT_PRESENTATION_MODE,
|
|
17
|
-
DEFAULT_RECENT_LIMIT,
|
|
18
18
|
PresentationMode,
|
|
19
19
|
commandId,
|
|
20
20
|
panelWidgetId
|
package/src/providers/claude.ts
CHANGED
|
@@ -49,7 +49,7 @@ export const descriptor: IProviderDescriptor = {
|
|
|
49
49
|
// `-n <name>` at launch: Claude stamps the name itself and re-stamps it on
|
|
50
50
|
// every turn, which is the only way a fork's name survives - a title written
|
|
51
51
|
// after the fact loses to the parent title the fork inherits.
|
|
52
|
-
|
|
52
|
+
promptsForBranchName: true,
|
|
53
53
|
mintsNewSessionId: true,
|
|
54
54
|
launchModes: [
|
|
55
55
|
{
|
package/src/providers/codex.ts
CHANGED
|
@@ -45,7 +45,7 @@ export const descriptor: IProviderDescriptor = {
|
|
|
45
45
|
colourSource: 'none',
|
|
46
46
|
// Forks are unnamed - Codex has no naming flag and no writable title field,
|
|
47
47
|
// so the panel never asks for a name it could not stamp anywhere.
|
|
48
|
-
|
|
48
|
+
promptsForBranchName: false,
|
|
49
49
|
// A bare `codex` mints its own thread id; there is no `--session-id`.
|
|
50
50
|
mintsNewSessionId: false,
|
|
51
51
|
launchModes: [
|
package/src/providers/gemini.ts
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
// * `colourSource: 'none'` - no colour command, and nothing in the chat store
|
|
10
10
|
// records one, so a tab tint only ever comes from the extension's own
|
|
11
11
|
// write-back store
|
|
12
|
-
// * `
|
|
13
|
-
// chat file's `summary`,
|
|
14
|
-
// conversation under; there is no naming flag to pass it to
|
|
12
|
+
// * `promptsForBranchName: true` - the branch flow asks for a name, which the
|
|
13
|
+
// server stamps into the copied chat file's `summary`, the field the CLI
|
|
14
|
+
// itself displays a conversation under; there is no naming flag to pass it to
|
|
15
15
|
// * `mintsNewSessionId: true` - `--session-id <uuid>` starts a fresh
|
|
16
16
|
// conversation under an id we chose, so a new session is identifiable from
|
|
17
17
|
// its argv on the first poll instead of being an unknown terminal
|
|
@@ -51,7 +51,7 @@ export const descriptor: IProviderDescriptor = {
|
|
|
51
51
|
colourSource: 'none',
|
|
52
52
|
// The fork's name is written into the copy by the server; the CLI has no
|
|
53
53
|
// naming flag to take it.
|
|
54
|
-
|
|
54
|
+
promptsForBranchName: true,
|
|
55
55
|
// `--session-id <uuid>` starts a conversation under an id we mint.
|
|
56
56
|
mintsNewSessionId: true,
|
|
57
57
|
launchModes: [
|
package/src/providers/kimi.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// * `colourSource: 'derived'` - kimi has no `/color`, so the default tint is a
|
|
10
10
|
// stable hash of the conversation id (a user-set colour still wins, per the
|
|
11
11
|
// Colour section of the acceptance criteria)
|
|
12
|
-
// * `
|
|
13
|
-
// `state.json`, not passed to the CLI
|
|
12
|
+
// * `promptsForBranchName: true` - the branch flow asks for a name, which the
|
|
13
|
+
// server stamps into the copied `state.json`, not passed to the CLI
|
|
14
14
|
// * one launch mode, `--yolo`, under kimi's own name
|
|
15
15
|
//
|
|
16
16
|
// The only hook is the branch label: kimi's conversation titles are
|
|
@@ -124,7 +124,7 @@ export const descriptor: IProviderDescriptor = {
|
|
|
124
124
|
colourSource: 'derived',
|
|
125
125
|
// The fork's name is written into the copy's `state.json` by the server; the
|
|
126
126
|
// CLI has no naming flag to take it.
|
|
127
|
-
|
|
127
|
+
promptsForBranchName: true,
|
|
128
128
|
// A bare `kimi` mints its own id and writes it on the first turn, and `-S`
|
|
129
129
|
// only ever resumes - so a new conversation cannot be launched under an id
|
|
130
130
|
// we chose, and is identified from the store on the next poll instead.
|