@plmbr/notebook-intelligence 5.0.0

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.
Files changed (137) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +412 -0
  3. package/lib/api.d.ts +288 -0
  4. package/lib/api.js +927 -0
  5. package/lib/cell-output-bundle.d.ts +25 -0
  6. package/lib/cell-output-bundle.js +129 -0
  7. package/lib/cell-output-toolbar.d.ts +26 -0
  8. package/lib/cell-output-toolbar.js +188 -0
  9. package/lib/chat-progress-feedback.d.ts +3 -0
  10. package/lib/chat-progress-feedback.js +27 -0
  11. package/lib/chat-sidebar.d.ts +92 -0
  12. package/lib/chat-sidebar.js +3452 -0
  13. package/lib/command-ids.d.ts +39 -0
  14. package/lib/command-ids.js +44 -0
  15. package/lib/components/ask-user-question.d.ts +2 -0
  16. package/lib/components/ask-user-question.js +85 -0
  17. package/lib/components/checkbox.d.ts +2 -0
  18. package/lib/components/checkbox.js +30 -0
  19. package/lib/components/claude-mcp-panel.d.ts +2 -0
  20. package/lib/components/claude-mcp-panel.js +275 -0
  21. package/lib/components/claude-mcp-paste.d.ts +7 -0
  22. package/lib/components/claude-mcp-paste.js +104 -0
  23. package/lib/components/claude-session-picker.d.ts +8 -0
  24. package/lib/components/claude-session-picker.js +127 -0
  25. package/lib/components/form-dialog.d.ts +25 -0
  26. package/lib/components/form-dialog.js +35 -0
  27. package/lib/components/launcher-picker.d.ts +6 -0
  28. package/lib/components/launcher-picker.js +135 -0
  29. package/lib/components/mcp-util.d.ts +2 -0
  30. package/lib/components/mcp-util.js +37 -0
  31. package/lib/components/notebook-generation-popover.d.ts +7 -0
  32. package/lib/components/notebook-generation-popover.js +60 -0
  33. package/lib/components/pill.d.ts +2 -0
  34. package/lib/components/pill.js +5 -0
  35. package/lib/components/plugins-panel.d.ts +3 -0
  36. package/lib/components/plugins-panel.js +466 -0
  37. package/lib/components/settings-panel.d.ts +11 -0
  38. package/lib/components/settings-panel.js +742 -0
  39. package/lib/components/skills-panel.d.ts +2 -0
  40. package/lib/components/skills-panel.js +1264 -0
  41. package/lib/handler.d.ts +8 -0
  42. package/lib/handler.js +36 -0
  43. package/lib/icons.d.ts +45 -0
  44. package/lib/icons.js +54 -0
  45. package/lib/index.d.ts +8 -0
  46. package/lib/index.js +2079 -0
  47. package/lib/markdown-renderer.d.ts +10 -0
  48. package/lib/markdown-renderer.js +64 -0
  49. package/lib/notebook-generation-toolbar.d.ts +16 -0
  50. package/lib/notebook-generation-toolbar.js +197 -0
  51. package/lib/notebook-generation.d.ts +8 -0
  52. package/lib/notebook-generation.js +12 -0
  53. package/lib/open-file-refresh-watcher-env.d.ts +4 -0
  54. package/lib/open-file-refresh-watcher-env.js +33 -0
  55. package/lib/open-file-refresh-watcher.d.ts +97 -0
  56. package/lib/open-file-refresh-watcher.js +190 -0
  57. package/lib/shell-utils.d.ts +6 -0
  58. package/lib/shell-utils.js +9 -0
  59. package/lib/task-target-notebook.d.ts +2 -0
  60. package/lib/task-target-notebook.js +28 -0
  61. package/lib/terminal-drag-format.d.ts +9 -0
  62. package/lib/terminal-drag-format.js +23 -0
  63. package/lib/terminal-drag.d.ts +12 -0
  64. package/lib/terminal-drag.js +268 -0
  65. package/lib/tokens.d.ts +149 -0
  66. package/lib/tokens.js +88 -0
  67. package/lib/tour/tour-anchors.d.ts +18 -0
  68. package/lib/tour/tour-anchors.js +18 -0
  69. package/lib/tour/tour-config.d.ts +66 -0
  70. package/lib/tour/tour-config.js +99 -0
  71. package/lib/tour/tour-defaults.json +58 -0
  72. package/lib/tour/tour-events.d.ts +19 -0
  73. package/lib/tour/tour-events.js +30 -0
  74. package/lib/tour/tour-overlay.d.ts +6 -0
  75. package/lib/tour/tour-overlay.js +350 -0
  76. package/lib/tour/tour-state.d.ts +20 -0
  77. package/lib/tour/tour-state.js +81 -0
  78. package/lib/tour/tour-steps.d.ts +33 -0
  79. package/lib/tour/tour-steps.js +216 -0
  80. package/lib/utils.d.ts +53 -0
  81. package/lib/utils.js +385 -0
  82. package/package.json +258 -0
  83. package/schema/plugin.json +42 -0
  84. package/src/api.ts +1424 -0
  85. package/src/cell-output-bundle.ts +176 -0
  86. package/src/cell-output-toolbar.ts +232 -0
  87. package/src/chat-progress-feedback.ts +35 -0
  88. package/src/chat-sidebar.tsx +5147 -0
  89. package/src/command-ids.ts +67 -0
  90. package/src/components/ask-user-question.tsx +151 -0
  91. package/src/components/checkbox.tsx +62 -0
  92. package/src/components/claude-mcp-panel.tsx +543 -0
  93. package/src/components/claude-mcp-paste.ts +132 -0
  94. package/src/components/claude-session-picker.tsx +214 -0
  95. package/src/components/form-dialog.tsx +75 -0
  96. package/src/components/launcher-picker.tsx +237 -0
  97. package/src/components/mcp-util.ts +53 -0
  98. package/src/components/notebook-generation-popover.tsx +127 -0
  99. package/src/components/pill.tsx +15 -0
  100. package/src/components/plugins-panel.tsx +774 -0
  101. package/src/components/settings-panel.tsx +1631 -0
  102. package/src/components/skills-panel.tsx +2084 -0
  103. package/src/handler.ts +51 -0
  104. package/src/icons.ts +71 -0
  105. package/src/index.ts +2583 -0
  106. package/src/markdown-renderer.tsx +153 -0
  107. package/src/notebook-generation-toolbar.tsx +281 -0
  108. package/src/notebook-generation.ts +23 -0
  109. package/src/open-file-refresh-watcher-env.ts +52 -0
  110. package/src/open-file-refresh-watcher.ts +260 -0
  111. package/src/shell-utils.ts +10 -0
  112. package/src/svg.d.ts +4 -0
  113. package/src/task-target-notebook.ts +37 -0
  114. package/src/terminal-drag-format.ts +29 -0
  115. package/src/terminal-drag.ts +382 -0
  116. package/src/tokens.ts +171 -0
  117. package/src/tour/tour-anchors.ts +21 -0
  118. package/src/tour/tour-config.ts +160 -0
  119. package/src/tour/tour-events.ts +34 -0
  120. package/src/tour/tour-overlay.tsx +474 -0
  121. package/src/tour/tour-state.ts +87 -0
  122. package/src/tour/tour-steps.ts +281 -0
  123. package/src/utils.ts +455 -0
  124. package/style/base.css +3238 -0
  125. package/style/icons/cell-toolbar-bug.svg +5 -0
  126. package/style/icons/cell-toolbar-chat.svg +5 -0
  127. package/style/icons/cell-toolbar-sparkle.svg +5 -0
  128. package/style/icons/claude.svg +1 -0
  129. package/style/icons/copilot-warning.svg +1 -0
  130. package/style/icons/copilot.svg +1 -0
  131. package/style/icons/copy.svg +1 -0
  132. package/style/icons/openai.svg +1 -0
  133. package/style/icons/opencode.svg +1 -0
  134. package/style/icons/sparkles-warning.svg +5 -0
  135. package/style/icons/sparkles.svg +1 -0
  136. package/style/index.css +1 -0
  137. package/style/index.js +1 -0
package/lib/api.js ADDED
@@ -0,0 +1,927 @@
1
+ // Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
2
+ var _a;
3
+ import { ServerConnection } from '@jupyterlab/services';
4
+ import { requestAPI } from './handler';
5
+ import { URLExt } from '@jupyterlab/coreutils';
6
+ import { Signal } from '@lumino/signaling';
7
+ import { GITHUB_COPILOT_PROVIDER_ID, RequestDataType, BackendMessageType, AssistantMode } from './tokens';
8
+ export var GitHubCopilotLoginStatus;
9
+ (function (GitHubCopilotLoginStatus) {
10
+ GitHubCopilotLoginStatus["NotLoggedIn"] = "NOT_LOGGED_IN";
11
+ GitHubCopilotLoginStatus["ActivatingDevice"] = "ACTIVATING_DEVICE";
12
+ GitHubCopilotLoginStatus["LoggingIn"] = "LOGGING_IN";
13
+ GitHubCopilotLoginStatus["LoggedIn"] = "LOGGED_IN";
14
+ })(GitHubCopilotLoginStatus || (GitHubCopilotLoginStatus = {}));
15
+ export var ClaudeModelType;
16
+ (function (ClaudeModelType) {
17
+ ClaudeModelType["None"] = "none";
18
+ ClaudeModelType["Inherit"] = "inherit";
19
+ ClaudeModelType["Default"] = "";
20
+ })(ClaudeModelType || (ClaudeModelType = {}));
21
+ export var ClaudeToolType;
22
+ (function (ClaudeToolType) {
23
+ ClaudeToolType["ClaudeCodeTools"] = "claude-code:built-in-tools";
24
+ ClaudeToolType["JupyterUITools"] = "nbi:built-in-jupyter-ui-tools";
25
+ })(ClaudeToolType || (ClaudeToolType = {}));
26
+ function claudeMCPServerFromWire(wire) {
27
+ var _b, _c, _d, _e, _f;
28
+ return {
29
+ name: String((_b = wire === null || wire === void 0 ? void 0 : wire.name) !== null && _b !== void 0 ? _b : ''),
30
+ scope: ((_c = wire === null || wire === void 0 ? void 0 : wire.scope) !== null && _c !== void 0 ? _c : 'user'),
31
+ transport: String((_d = wire === null || wire === void 0 ? void 0 : wire.transport) !== null && _d !== void 0 ? _d : 'stdio'),
32
+ command: String((_e = wire === null || wire === void 0 ? void 0 : wire.command) !== null && _e !== void 0 ? _e : ''),
33
+ args: Array.isArray(wire === null || wire === void 0 ? void 0 : wire.args) ? wire.args.map(String) : [],
34
+ env: (wire === null || wire === void 0 ? void 0 : wire.env) && typeof wire.env === 'object'
35
+ ? Object.fromEntries(Object.entries(wire.env).map(([k, v]) => [String(k), String(v)]))
36
+ : {},
37
+ url: String((_f = wire === null || wire === void 0 ? void 0 : wire.url) !== null && _f !== void 0 ? _f : ''),
38
+ headers: (wire === null || wire === void 0 ? void 0 : wire.headers) && typeof wire.headers === 'object'
39
+ ? Object.fromEntries(Object.entries(wire.headers).map(([k, v]) => [String(k), String(v)]))
40
+ : {},
41
+ disabledForWorkspace: Boolean(wire === null || wire === void 0 ? void 0 : wire.disabled_for_workspace)
42
+ };
43
+ }
44
+ // Exported for direct testing of the wire-format contract. The snake_case
45
+ // keys it consumes (managed_source, managed_ref, tracks_upstream,
46
+ // tracking_ref, allowed_tools) are the load-bearing JSON shape between
47
+ // the Tornado handlers and the panel; a typo here would silently corrupt
48
+ // user state ("I toggled it on but it didn't stick").
49
+ export function skillFromWire(wire) {
50
+ var _b, _c, _d, _e, _f, _g, _h;
51
+ return {
52
+ scope: wire.scope,
53
+ name: wire.name,
54
+ description: wire.description,
55
+ allowedTools: (_b = wire.allowed_tools) !== null && _b !== void 0 ? _b : [],
56
+ rootPath: wire.root_path,
57
+ files: (_c = wire.files) !== null && _c !== void 0 ? _c : [],
58
+ source: (_d = wire.source) !== null && _d !== void 0 ? _d : '',
59
+ managed: Boolean(wire.managed),
60
+ managedSource: (_e = wire.managed_source) !== null && _e !== void 0 ? _e : '',
61
+ managedRef: (_f = wire.managed_ref) !== null && _f !== void 0 ? _f : '',
62
+ tracksUpstream: Boolean(wire.tracks_upstream),
63
+ trackingRef: (_g = wire.tracking_ref) !== null && _g !== void 0 ? _g : '',
64
+ body: (_h = wire.body) !== null && _h !== void 0 ? _h : ''
65
+ };
66
+ }
67
+ function claudeModelFromWire(wire) {
68
+ return {
69
+ id: wire.id,
70
+ name: wire.name,
71
+ contextWindow: wire.context_window
72
+ };
73
+ }
74
+ // Shared frozen object returned by NBIConfig.tourOverrides when no
75
+ // admin overrides are present. Stable identity matters for downstream
76
+ // consumers (memoized command-palette label, useMemo deps).
77
+ const EMPTY_TOUR_OVERRIDES = Object.freeze({});
78
+ export class NBIConfig {
79
+ constructor() {
80
+ this.capabilities = {};
81
+ this.chatParticipants = [];
82
+ this.changed = new Signal(this);
83
+ }
84
+ get userHomeDir() {
85
+ return this.capabilities.user_home_dir;
86
+ }
87
+ get userConfigDir() {
88
+ return this.capabilities.nbi_user_config_dir;
89
+ }
90
+ get llmProviders() {
91
+ return this.capabilities.llm_providers;
92
+ }
93
+ get chatModels() {
94
+ return this.capabilities.chat_models;
95
+ }
96
+ get inlineCompletionModels() {
97
+ return this.capabilities.inline_completion_models;
98
+ }
99
+ get defaultChatMode() {
100
+ return this.capabilities.default_chat_mode;
101
+ }
102
+ get chatModel() {
103
+ return this.capabilities.chat_model;
104
+ }
105
+ get chatModelSupportsVision() {
106
+ return this.capabilities.chat_model_supports_vision === true;
107
+ }
108
+ get inlineCompletionModel() {
109
+ return this.capabilities.inline_completion_model;
110
+ }
111
+ get usingGitHubCopilotModel() {
112
+ return (this.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID ||
113
+ this.inlineCompletionModel.provider === GITHUB_COPILOT_PROVIDER_ID);
114
+ }
115
+ get storeGitHubAccessToken() {
116
+ return this.capabilities.store_github_access_token === true;
117
+ }
118
+ get inlineCompletionDebouncerDelay() {
119
+ return Number.isInteger(this.capabilities.inline_completion_debouncer_delay)
120
+ ? this.capabilities.inline_completion_debouncer_delay
121
+ : 200;
122
+ }
123
+ get toolConfig() {
124
+ return this.capabilities.tool_config;
125
+ }
126
+ get mcpServers() {
127
+ return this.toolConfig.mcpServers;
128
+ }
129
+ getMCPServer(serverId) {
130
+ return this.toolConfig.mcpServers.find((server) => server.id === serverId);
131
+ }
132
+ getMCPServerPrompt(serverId, promptName) {
133
+ const server = this.getMCPServer(serverId);
134
+ if (server) {
135
+ return server.prompts.find((prompt) => prompt.name === promptName);
136
+ }
137
+ return null;
138
+ }
139
+ get mcpServerSettings() {
140
+ return this.capabilities.mcp_server_settings;
141
+ }
142
+ get claudeSettings() {
143
+ return this.capabilities.claude_settings;
144
+ }
145
+ get claudeModels() {
146
+ var _b;
147
+ return ((_b = this.capabilities.claude_models) !== null && _b !== void 0 ? _b : []).map(claudeModelFromWire);
148
+ }
149
+ get isInClaudeCodeMode() {
150
+ return this.claudeSettings.enabled === true;
151
+ }
152
+ get isClaudeCliAvailable() {
153
+ return this.capabilities.claude_cli_available === true;
154
+ }
155
+ get isOpenCodeCliAvailable() {
156
+ return this.capabilities.opencode_cli_available === true;
157
+ }
158
+ get isPiCliAvailable() {
159
+ return this.capabilities.pi_cli_available === true;
160
+ }
161
+ get isGitHubCopilotCliAvailable() {
162
+ return this.capabilities.github_copilot_cli_available === true;
163
+ }
164
+ get isCodexCliAvailable() {
165
+ return this.capabilities.codex_cli_available === true;
166
+ }
167
+ isCodingAgentLauncherDisabledByPolicy(launcherId) {
168
+ // Fail closed when the field is missing or malformed: an admin denylist
169
+ // must not silently disappear if capabilities haven't loaded yet or a
170
+ // backend regression drops the field. The companion `is*CliAvailable`
171
+ // getters already default to false until capabilities arrive, so on
172
+ // first paint the tile is hidden regardless; this just ensures the
173
+ // policy gate stays in effect even if a future change pre-seeds those
174
+ // flags.
175
+ const list = this.capabilities.disabled_coding_agent_launchers;
176
+ if (Array.isArray(list)) {
177
+ return list.includes(launcherId);
178
+ }
179
+ return true;
180
+ }
181
+ get chatFeedbackEnabled() {
182
+ return this.capabilities.chat_feedback_enabled === true;
183
+ }
184
+ // Admin-supplied tour-copy overrides, served from the capabilities
185
+ // response after server-side validation. Returns the raw dict; the
186
+ // tour module decides how to apply it. Defaults to a shared frozen
187
+ // empty object so callers can spread/access keys without
188
+ // null-checking AND the getter doesn't allocate a fresh `{}` on every
189
+ // read (the JupyterLab command palette polls a command's label
190
+ // thunk on every keystroke, so identity stability matters).
191
+ get tourOverrides() {
192
+ const v = this.capabilities.tour_overrides;
193
+ return v && typeof v === 'object' ? v : EMPTY_TOUR_OVERRIDES;
194
+ }
195
+ get allowGithubSkillImport() {
196
+ return this.capabilities.allow_github_skill_import !== false;
197
+ }
198
+ get additionalSkippedWorkspaceDirectories() {
199
+ const v = this.capabilities.additional_skipped_workspace_directories;
200
+ return Array.isArray(v) ? v : [];
201
+ }
202
+ get allowGithubPluginImport() {
203
+ // Default-open: missing/undefined means the org hasn't gated this, so
204
+ // older backends without the flag continue to allow the GitHub
205
+ // affordance. Mirrors `cellOutputFeatures` polarity.
206
+ return this.capabilities.allow_github_plugin_import !== false;
207
+ }
208
+ get cellOutputFeatures() {
209
+ var _b, _c, _d, _e, _f, _g, _h;
210
+ const v = (_b = this.capabilities.cell_output_features) !== null && _b !== void 0 ? _b : {};
211
+ return {
212
+ explain_error: {
213
+ enabled: ((_c = v.explain_error) === null || _c === void 0 ? void 0 : _c.enabled) !== false,
214
+ locked: ((_d = v.explain_error) === null || _d === void 0 ? void 0 : _d.locked) === true
215
+ },
216
+ output_followup: {
217
+ enabled: ((_e = v.output_followup) === null || _e === void 0 ? void 0 : _e.enabled) !== false,
218
+ locked: ((_f = v.output_followup) === null || _f === void 0 ? void 0 : _f.locked) === true
219
+ },
220
+ output_toolbar: {
221
+ enabled: ((_g = v.output_toolbar) === null || _g === void 0 ? void 0 : _g.enabled) !== false,
222
+ locked: ((_h = v.output_toolbar) === null || _h === void 0 ? void 0 : _h.locked) === true
223
+ }
224
+ };
225
+ }
226
+ get featurePolicies() {
227
+ var _b;
228
+ const v = (_b = this.capabilities.feature_policies) !== null && _b !== void 0 ? _b : {};
229
+ const names = [
230
+ 'explain_error',
231
+ 'output_followup',
232
+ 'output_toolbar',
233
+ 'claude_mode',
234
+ 'claude_continue_conversation',
235
+ 'claude_code_tools',
236
+ 'claude_jupyter_ui_tools',
237
+ 'claude_setting_source_user',
238
+ 'claude_setting_source_project',
239
+ 'store_github_access_token',
240
+ 'skills_management',
241
+ 'claude_mcp_management',
242
+ 'claude_plugins_management',
243
+ 'terminal_drag_drop',
244
+ 'refresh_open_files_on_disk_change'
245
+ ];
246
+ // Policies that default *open* when the capability field is missing,
247
+ // covering two cases: admin-only management gates (no user toggle) where
248
+ // a new frontend on an older backend must keep the tab visible, and the
249
+ // open-files refresh watcher whose documented default is on so its
250
+ // first ticks before capabilities land don't silently no-op. The other
251
+ // policies pair with a user toggle and default closed (missing means
252
+ // "no user pref recorded yet, treat as off").
253
+ const defaultOpen = new Set([
254
+ 'skills_management',
255
+ 'claude_mcp_management',
256
+ 'claude_plugins_management',
257
+ 'refresh_open_files_on_disk_change'
258
+ ]);
259
+ const result = {};
260
+ for (const name of names) {
261
+ const entry = v[name];
262
+ // Strict polarity: only default-open when the entry is wholly absent
263
+ // (old backend). A malformed entry (string "false", null, missing
264
+ // `enabled` field) falls through to closed for default-closed gates
265
+ // and stays open only when the field is explicitly true for default-open
266
+ // gates — never silently land in the open bucket.
267
+ let enabled;
268
+ if (entry === undefined) {
269
+ enabled = defaultOpen.has(name);
270
+ }
271
+ else {
272
+ enabled = entry.enabled === true;
273
+ }
274
+ result[name] = {
275
+ enabled,
276
+ locked: (entry === null || entry === void 0 ? void 0 : entry.locked) === true
277
+ };
278
+ }
279
+ return result;
280
+ }
281
+ get settingLocks() {
282
+ var _b, _c;
283
+ const v = (_b = this.capabilities.setting_locks) !== null && _b !== void 0 ? _b : {};
284
+ const names = [
285
+ 'chat_model_provider',
286
+ 'chat_model_id',
287
+ 'inline_completion_model_provider',
288
+ 'inline_completion_model_id',
289
+ 'claude_chat_model',
290
+ 'claude_inline_completion_model',
291
+ 'claude_api_key',
292
+ 'claude_base_url'
293
+ ];
294
+ const result = {};
295
+ for (const name of names) {
296
+ result[name] = { locked: ((_c = v[name]) === null || _c === void 0 ? void 0 : _c.locked) === true };
297
+ }
298
+ return result;
299
+ }
300
+ }
301
+ class NBIAPI {
302
+ static async initialize() {
303
+ await this.fetchCapabilities();
304
+ this.updateGitHubLoginStatus();
305
+ NBIAPI.initializeWebsocket();
306
+ this._messageReceived.connect((_, msg) => {
307
+ msg = JSON.parse(msg);
308
+ if (msg.type === BackendMessageType.MCPServerStatusChange ||
309
+ msg.type === BackendMessageType.ClaudeCodeStatusChange) {
310
+ this.fetchCapabilities();
311
+ }
312
+ else if (msg.type === BackendMessageType.GitHubCopilotLoginStatusChange) {
313
+ // The Copilot chat-model catalogue is fetched lazily once the bearer
314
+ // token is minted (issue #258), so the model dropdown depends on a
315
+ // capabilities refresh in addition to the login-status update.
316
+ Promise.all([
317
+ this.updateGitHubLoginStatus(),
318
+ this.fetchCapabilities()
319
+ ]).then(() => {
320
+ this.githubLoginStatusChanged.emit();
321
+ });
322
+ }
323
+ else if (msg.type === BackendMessageType.SkillsReloaded) {
324
+ this.skillsReloaded.emit();
325
+ }
326
+ else if (msg.type === BackendMessageType.ClaudeCodeHeartbeat) {
327
+ this.claudeCodeHeartbeat.emit();
328
+ }
329
+ });
330
+ }
331
+ static async initializeWebsocket() {
332
+ const serverSettings = ServerConnection.makeSettings();
333
+ const wsUrl = URLExt.join(serverSettings.wsUrl, 'notebook-intelligence', 'copilot');
334
+ this._webSocket = new serverSettings.WebSocket(wsUrl);
335
+ this._webSocket.onmessage = msg => {
336
+ this._messageReceived.emit(msg.data);
337
+ };
338
+ this._webSocket.onerror = msg => {
339
+ console.error(`Websocket error: ${msg}. Closing...`);
340
+ this._webSocket.close();
341
+ };
342
+ this._webSocket.onclose = msg => {
343
+ console.log(`Websocket is closed: ${msg.reason}. Reconnecting...`);
344
+ setTimeout(() => {
345
+ NBIAPI.initializeWebsocket();
346
+ }, 1000);
347
+ };
348
+ }
349
+ static getLoginStatus() {
350
+ return this._loginStatus;
351
+ }
352
+ static getDeviceVerificationInfo() {
353
+ return this._deviceVerificationInfo;
354
+ }
355
+ static getGHLoginRequired() {
356
+ return (this.config.usingGitHubCopilotModel &&
357
+ NBIAPI.getLoginStatus() === GitHubCopilotLoginStatus.NotLoggedIn);
358
+ }
359
+ static getChatEnabled() {
360
+ return (this.config.isInClaudeCodeMode ||
361
+ (this.config.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID
362
+ ? !this.getGHLoginRequired()
363
+ : this.config.llmProviders.find(provider => provider.id === this.config.chatModel.provider)));
364
+ }
365
+ static getInlineCompletionEnabled() {
366
+ return (this.config.isInClaudeCodeMode ||
367
+ (this.config.inlineCompletionModel.provider === GITHUB_COPILOT_PROVIDER_ID
368
+ ? !this.getGHLoginRequired()
369
+ : this.config.llmProviders.find(provider => provider.id === this.config.inlineCompletionModel.provider)));
370
+ }
371
+ static async loginToGitHub() {
372
+ this._loginStatus = GitHubCopilotLoginStatus.ActivatingDevice;
373
+ return new Promise((resolve, reject) => {
374
+ requestAPI('gh-login', { method: 'POST' })
375
+ .then(data => {
376
+ resolve({
377
+ verificationURI: data.verification_uri,
378
+ userCode: data.user_code
379
+ });
380
+ this.updateGitHubLoginStatus();
381
+ })
382
+ .catch(reason => {
383
+ console.error(`Failed to login to GitHub Copilot.\n${reason}`);
384
+ reject(reason);
385
+ });
386
+ });
387
+ }
388
+ static async logoutFromGitHub() {
389
+ this._loginStatus = GitHubCopilotLoginStatus.ActivatingDevice;
390
+ return new Promise((resolve, reject) => {
391
+ requestAPI('gh-logout', { method: 'GET' })
392
+ .then(data => {
393
+ this.updateGitHubLoginStatus().then(() => {
394
+ resolve(data);
395
+ });
396
+ })
397
+ .catch(reason => {
398
+ console.error(`Failed to logout from GitHub Copilot.\n${reason}`);
399
+ reject(reason);
400
+ });
401
+ });
402
+ }
403
+ static async updateGitHubLoginStatus() {
404
+ return new Promise((resolve, reject) => {
405
+ requestAPI('gh-login-status')
406
+ .then(response => {
407
+ this._loginStatus = response.status;
408
+ this._deviceVerificationInfo.verificationURI =
409
+ response.verification_uri || '';
410
+ this._deviceVerificationInfo.userCode = response.user_code || '';
411
+ resolve();
412
+ })
413
+ .catch(reason => {
414
+ console.error(`Failed to fetch GitHub Copilot login status.\n${reason}`);
415
+ reject(reason);
416
+ });
417
+ });
418
+ }
419
+ static async fetchCapabilities() {
420
+ return new Promise((resolve, reject) => {
421
+ requestAPI('capabilities', { method: 'GET' })
422
+ .then(data => {
423
+ const oldConfig = {
424
+ capabilities: structuredClone(this.config.capabilities),
425
+ chatParticipants: structuredClone(this.config.chatParticipants)
426
+ };
427
+ this.config.capabilities = structuredClone(data);
428
+ this.config.chatParticipants = structuredClone(data.chat_participants);
429
+ const newConfig = {
430
+ capabilities: structuredClone(this.config.capabilities),
431
+ chatParticipants: structuredClone(this.config.chatParticipants)
432
+ };
433
+ if (JSON.stringify(newConfig) !== JSON.stringify(oldConfig)) {
434
+ this.configChanged.emit();
435
+ }
436
+ resolve();
437
+ })
438
+ .catch(reason => {
439
+ console.error(`Failed to get extension capabilities.\n${reason}`);
440
+ reject(reason);
441
+ });
442
+ });
443
+ }
444
+ static async setConfig(config) {
445
+ requestAPI('config', {
446
+ method: 'POST',
447
+ body: JSON.stringify(config)
448
+ })
449
+ .then(data => {
450
+ NBIAPI.fetchCapabilities();
451
+ })
452
+ .catch(reason => {
453
+ console.error(`Failed to set NBI config.\n${reason}`);
454
+ });
455
+ }
456
+ static async updateOllamaModelList() {
457
+ return new Promise((resolve, reject) => {
458
+ requestAPI('update-provider-models', {
459
+ method: 'POST',
460
+ body: JSON.stringify({ provider: 'ollama' })
461
+ })
462
+ .then(async (data) => {
463
+ await NBIAPI.fetchCapabilities();
464
+ resolve();
465
+ })
466
+ .catch(reason => {
467
+ console.error(`Failed to update ollama model list.\n${reason}`);
468
+ reject(reason);
469
+ });
470
+ });
471
+ }
472
+ static async updateClaudeModelList() {
473
+ return new Promise((resolve, reject) => {
474
+ requestAPI('update-provider-models', {
475
+ method: 'POST',
476
+ body: JSON.stringify({ provider: 'claude' })
477
+ })
478
+ .then(async (data) => {
479
+ await NBIAPI.fetchCapabilities();
480
+ resolve();
481
+ })
482
+ .catch(reason => {
483
+ console.error(`Failed to update Claude model list.\n${reason}`);
484
+ reject(reason);
485
+ });
486
+ });
487
+ }
488
+ static async getMCPConfigFile() {
489
+ return new Promise((resolve, reject) => {
490
+ requestAPI('mcp-config-file', { method: 'GET' })
491
+ .then(async (data) => {
492
+ resolve(data);
493
+ })
494
+ .catch(reason => {
495
+ console.error(`Failed to get MCP config file.\n${reason}`);
496
+ reject(reason);
497
+ });
498
+ });
499
+ }
500
+ static async setMCPConfigFile(config) {
501
+ return new Promise((resolve, reject) => {
502
+ requestAPI('mcp-config-file', {
503
+ method: 'POST',
504
+ body: JSON.stringify(config)
505
+ })
506
+ .then(async (data) => {
507
+ resolve(data);
508
+ })
509
+ .catch(reason => {
510
+ console.error(`Failed to set MCP config file.\n${reason}`);
511
+ reject(reason);
512
+ });
513
+ });
514
+ }
515
+ static async listSkills() {
516
+ var _b;
517
+ const data = await requestAPI('skills', { method: 'GET' });
518
+ return ((_b = data.skills) !== null && _b !== void 0 ? _b : []).map(skillFromWire);
519
+ }
520
+ static async getSkillsContext() {
521
+ var _b, _c, _d, _e;
522
+ const data = await requestAPI('skills/context', { method: 'GET' });
523
+ return {
524
+ projectRoot: (_b = data.project_root) !== null && _b !== void 0 ? _b : '',
525
+ projectName: (_c = data.project_name) !== null && _c !== void 0 ? _c : '',
526
+ userSkillsDir: (_d = data.user_skills_dir) !== null && _d !== void 0 ? _d : '',
527
+ projectSkillsDir: (_e = data.project_skills_dir) !== null && _e !== void 0 ? _e : ''
528
+ };
529
+ }
530
+ static async readSkill(scope, name) {
531
+ const data = await requestAPI(`skills/${scope}/${encodeURIComponent(name)}`, { method: 'GET' });
532
+ return skillFromWire(data.skill);
533
+ }
534
+ static async createSkill(payload) {
535
+ const data = await requestAPI('skills', {
536
+ method: 'POST',
537
+ body: JSON.stringify({
538
+ scope: payload.scope,
539
+ name: payload.name,
540
+ description: payload.description,
541
+ allowed_tools: payload.allowedTools,
542
+ body: payload.body
543
+ })
544
+ });
545
+ return skillFromWire(data.skill);
546
+ }
547
+ static async updateSkill(scope, name, payload) {
548
+ const wire = {};
549
+ if (payload.description !== undefined) {
550
+ wire.description = payload.description;
551
+ }
552
+ if (payload.allowedTools !== undefined) {
553
+ wire.allowed_tools = payload.allowedTools;
554
+ }
555
+ if (payload.body !== undefined) {
556
+ wire.body = payload.body;
557
+ }
558
+ if (payload.tracksUpstream !== undefined) {
559
+ wire.tracks_upstream = payload.tracksUpstream;
560
+ }
561
+ const data = await requestAPI(`skills/${scope}/${encodeURIComponent(name)}`, {
562
+ method: 'PUT',
563
+ body: JSON.stringify(wire)
564
+ });
565
+ return skillFromWire(data.skill);
566
+ }
567
+ static async deleteSkill(scope, name) {
568
+ await requestAPI(`skills/${scope}/${encodeURIComponent(name)}`, {
569
+ method: 'DELETE'
570
+ });
571
+ }
572
+ static async previewSkillImport(url) {
573
+ var _b, _c, _d, _e, _f, _g;
574
+ const data = await requestAPI('skills/import/preview', {
575
+ method: 'POST',
576
+ body: JSON.stringify({ url })
577
+ });
578
+ const p = data.preview;
579
+ return {
580
+ name: p.name,
581
+ description: (_b = p.description) !== null && _b !== void 0 ? _b : '',
582
+ allowedTools: (_c = p.allowed_tools) !== null && _c !== void 0 ? _c : [],
583
+ body: (_d = p.body) !== null && _d !== void 0 ? _d : '',
584
+ files: (_e = p.files) !== null && _e !== void 0 ? _e : [],
585
+ sourceUrl: (_f = p.source_url) !== null && _f !== void 0 ? _f : '',
586
+ canonicalUrl: (_g = p.canonical_url) !== null && _g !== void 0 ? _g : '',
587
+ existsInUserScope: p.exists_in_user_scope === true,
588
+ existsInProjectScope: p.exists_in_project_scope === true
589
+ };
590
+ }
591
+ static async importSkill(payload) {
592
+ const wire = { url: payload.url, scope: payload.scope };
593
+ if (payload.name) {
594
+ wire.name = payload.name;
595
+ }
596
+ if (payload.overwrite) {
597
+ wire.overwrite = true;
598
+ }
599
+ if (payload.tracksUpstream) {
600
+ wire.tracks_upstream = true;
601
+ }
602
+ const data = await requestAPI('skills/import', {
603
+ method: 'POST',
604
+ body: JSON.stringify(wire)
605
+ });
606
+ return skillFromWire(data.skill);
607
+ }
608
+ static async syncTrackingSkill(scope, name) {
609
+ var _b;
610
+ const data = await requestAPI(`skills/${scope}/${encodeURIComponent(name)}/sync`, { method: 'POST' });
611
+ return {
612
+ updated: Boolean(data.updated),
613
+ ref: (_b = data.ref) !== null && _b !== void 0 ? _b : ''
614
+ };
615
+ }
616
+ static async syncAllTrackingSkills() {
617
+ const data = await requestAPI('skills/sync-all-tracking', {
618
+ method: 'POST'
619
+ });
620
+ if (!Array.isArray(data === null || data === void 0 ? void 0 : data.results)) {
621
+ return [];
622
+ }
623
+ return data.results.map((r) => ({
624
+ scope: r.scope,
625
+ name: r.name,
626
+ updated: typeof r.updated === 'boolean' ? r.updated : undefined,
627
+ ref: typeof r.ref === 'string' ? r.ref : undefined,
628
+ error: typeof r.error === 'string' ? r.error : undefined
629
+ }));
630
+ }
631
+ static async listClaudeMCPServers() {
632
+ const data = await requestAPI('claude-mcp');
633
+ return Array.isArray(data === null || data === void 0 ? void 0 : data.servers)
634
+ ? data.servers.map(claudeMCPServerFromWire)
635
+ : [];
636
+ }
637
+ static async addClaudeMCPServer(input) {
638
+ const body = {
639
+ name: input.name,
640
+ scope: input.scope,
641
+ transport: input.transport,
642
+ command_or_url: input.commandOrUrl
643
+ };
644
+ if (input.args && input.args.length) {
645
+ body.args = input.args;
646
+ }
647
+ if (input.env && Object.keys(input.env).length) {
648
+ body.env = input.env;
649
+ }
650
+ if (input.headers && Object.keys(input.headers).length) {
651
+ body.headers = input.headers;
652
+ }
653
+ const data = await requestAPI('claude-mcp', {
654
+ method: 'POST',
655
+ body: JSON.stringify(body)
656
+ });
657
+ return claudeMCPServerFromWire(data.server);
658
+ }
659
+ static async removeClaudeMCPServer(name, scope) {
660
+ await requestAPI(`claude-mcp/${scope}/${encodeURIComponent(name)}`, {
661
+ method: 'DELETE'
662
+ });
663
+ }
664
+ static async setClaudeMCPServerDisabled(name, scope, disabled) {
665
+ const data = await requestAPI(`claude-mcp/${scope}/${encodeURIComponent(name)}`, {
666
+ method: 'PATCH',
667
+ body: JSON.stringify({ disabled_for_workspace: disabled })
668
+ });
669
+ return claudeMCPServerFromWire(data.server);
670
+ }
671
+ static async listPlugins() {
672
+ const data = await requestAPI('plugins');
673
+ return Array.isArray(data === null || data === void 0 ? void 0 : data.plugins) ? data.plugins : [];
674
+ }
675
+ static async installPlugin(plugin, scope = 'user') {
676
+ await requestAPI('plugins', {
677
+ method: 'POST',
678
+ body: JSON.stringify({ plugin, scope })
679
+ });
680
+ }
681
+ static async uninstallPlugin(plugin, scope = 'user') {
682
+ await requestAPI(`plugins/${scope}/${encodeURIComponent(plugin)}`, {
683
+ method: 'DELETE'
684
+ });
685
+ }
686
+ static async setPluginEnabled(plugin, scope, enabled) {
687
+ await requestAPI(`plugins/${scope}/${encodeURIComponent(plugin)}`, {
688
+ method: 'POST',
689
+ body: JSON.stringify({ action: enabled ? 'enable' : 'disable' })
690
+ });
691
+ }
692
+ static async listPluginMarketplaces() {
693
+ const data = await requestAPI('plugins/marketplace');
694
+ return Array.isArray(data === null || data === void 0 ? void 0 : data.marketplaces)
695
+ ? data.marketplaces
696
+ : [];
697
+ }
698
+ static async listPluginMarketplacePlugins(marketplace) {
699
+ const data = await requestAPI(`plugins/marketplace/${encodeURIComponent(marketplace)}/plugins`);
700
+ return Array.isArray(data === null || data === void 0 ? void 0 : data.plugins)
701
+ ? data.plugins
702
+ : [];
703
+ }
704
+ static async addPluginMarketplace(source, scope = 'user') {
705
+ await requestAPI('plugins/marketplace', {
706
+ method: 'POST',
707
+ body: JSON.stringify({ source, scope })
708
+ });
709
+ }
710
+ static async removePluginMarketplace(name) {
711
+ await requestAPI(`plugins/marketplace/${encodeURIComponent(name)}`, {
712
+ method: 'DELETE'
713
+ });
714
+ }
715
+ static async updatePluginMarketplace(name) {
716
+ await requestAPI(`plugins/marketplace/${encodeURIComponent(name)}/update`, {
717
+ method: 'POST',
718
+ body: '{}'
719
+ });
720
+ }
721
+ static async reconcileManagedSkills() {
722
+ var _b, _c, _d, _e;
723
+ const data = await requestAPI('skills/reconcile', {
724
+ method: 'POST'
725
+ });
726
+ return {
727
+ added: Number((_b = data.added) !== null && _b !== void 0 ? _b : 0),
728
+ updated: Number((_c = data.updated) !== null && _c !== void 0 ? _c : 0),
729
+ removed: Number((_d = data.removed) !== null && _d !== void 0 ? _d : 0),
730
+ unchanged: Number((_e = data.unchanged) !== null && _e !== void 0 ? _e : 0),
731
+ errors: Array.isArray(data.errors) ? data.errors.map(String) : []
732
+ };
733
+ }
734
+ static async renameSkill(scope, name, newName) {
735
+ const data = await requestAPI(`skills/${scope}/${encodeURIComponent(name)}/rename`, {
736
+ method: 'POST',
737
+ body: JSON.stringify({ new_name: newName })
738
+ });
739
+ return skillFromWire(data.skill);
740
+ }
741
+ static async readBundleFile(scope, name, path) {
742
+ const data = await requestAPI(`skills/${scope}/${encodeURIComponent(name)}/files?path=${encodeURIComponent(path)}`, { method: 'GET' });
743
+ return data.content;
744
+ }
745
+ static async writeBundleFile(scope, name, path, content) {
746
+ await requestAPI(`skills/${scope}/${encodeURIComponent(name)}/files?path=${encodeURIComponent(path)}`, {
747
+ method: 'PUT',
748
+ body: JSON.stringify({ content })
749
+ });
750
+ }
751
+ static async deleteBundleFile(scope, name, path) {
752
+ await requestAPI(`skills/${scope}/${encodeURIComponent(name)}/files?path=${encodeURIComponent(path)}`, { method: 'DELETE' });
753
+ }
754
+ static async renameBundleFile(scope, name, from, to) {
755
+ await requestAPI(`skills/${scope}/${encodeURIComponent(name)}/files/rename`, {
756
+ method: 'POST',
757
+ body: JSON.stringify({ from, to })
758
+ });
759
+ }
760
+ /**
761
+ * Subscribe to inbound websocket messages for a single request, forwarding
762
+ * them to `responseEmitter`. The subscription auto-disconnects when the
763
+ * server emits StreamEnd, preventing per-request listener accumulation.
764
+ */
765
+ static _subscribeUntilStreamEnd(messageId, responseEmitter) {
766
+ const handler = (_, msg) => {
767
+ const parsed = JSON.parse(msg);
768
+ if (parsed.id !== messageId) {
769
+ return;
770
+ }
771
+ responseEmitter.emit(parsed);
772
+ if (parsed.type === BackendMessageType.StreamEnd) {
773
+ this._messageReceived.disconnect(handler);
774
+ }
775
+ };
776
+ this._messageReceived.connect(handler);
777
+ }
778
+ static async chatRequest(messageId, chatId, prompt, language, currentDirectory, filename, additionalContext, chatMode, toolSelections, responseEmitter) {
779
+ this._subscribeUntilStreamEnd(messageId, responseEmitter);
780
+ this._webSocket.send(JSON.stringify({
781
+ id: messageId,
782
+ type: RequestDataType.ChatRequest,
783
+ data: {
784
+ chatId,
785
+ prompt,
786
+ language,
787
+ currentDirectory,
788
+ filename,
789
+ additionalContext,
790
+ chatMode,
791
+ toolSelections
792
+ }
793
+ }));
794
+ }
795
+ static async reloadMCPServers() {
796
+ return new Promise((resolve, reject) => {
797
+ requestAPI('reload-mcp-servers', { method: 'POST' })
798
+ .then(async (data) => {
799
+ await NBIAPI.fetchCapabilities();
800
+ resolve(data);
801
+ })
802
+ .catch(reason => {
803
+ console.error(`Failed to reload MCP servers.\n${reason}`);
804
+ reject(reason);
805
+ });
806
+ });
807
+ }
808
+ static async generateCode(messageId, chatId, prompt, prefix, suffix, existingCode, language, filename, responseEmitter) {
809
+ this._subscribeUntilStreamEnd(messageId, responseEmitter);
810
+ this._webSocket.send(JSON.stringify({
811
+ id: messageId,
812
+ type: RequestDataType.GenerateCode,
813
+ data: {
814
+ chatId,
815
+ prompt,
816
+ prefix,
817
+ suffix,
818
+ existingCode,
819
+ language,
820
+ filename
821
+ }
822
+ }));
823
+ }
824
+ static async sendChatUserInput(messageId, data) {
825
+ this._webSocket.send(JSON.stringify({
826
+ id: messageId,
827
+ type: RequestDataType.ChatUserInput,
828
+ data
829
+ }));
830
+ }
831
+ static async sendWebSocketMessage(messageId, messageType, data) {
832
+ this._webSocket.send(JSON.stringify({ id: messageId, type: messageType, data }));
833
+ }
834
+ static async inlineCompletionsRequest(chatId, messageId, prefix, suffix, language, filename, responseEmitter) {
835
+ this._subscribeUntilStreamEnd(messageId, responseEmitter);
836
+ this._webSocket.send(JSON.stringify({
837
+ id: messageId,
838
+ type: RequestDataType.InlineCompletionRequest,
839
+ data: {
840
+ chatId,
841
+ prefix,
842
+ suffix,
843
+ language,
844
+ filename
845
+ }
846
+ }));
847
+ }
848
+ static async uploadFile(file) {
849
+ const formData = new FormData();
850
+ formData.append('file', file, file.name);
851
+ return requestAPI('upload-file', {
852
+ method: 'POST',
853
+ body: formData
854
+ });
855
+ }
856
+ static async listClaudeSessions(scope = 'all') {
857
+ return new Promise((resolve, reject) => {
858
+ requestAPI(`claude-sessions?scope=${scope}`, {
859
+ method: 'GET'
860
+ })
861
+ .then(data => {
862
+ var _b, _c;
863
+ resolve({
864
+ sessions: (_b = data.sessions) !== null && _b !== void 0 ? _b : [],
865
+ currentCwd: (_c = data.current_cwd) !== null && _c !== void 0 ? _c : ''
866
+ });
867
+ })
868
+ .catch(reason => {
869
+ console.error(`Failed to list Claude sessions.\n${reason}`);
870
+ reject(reason);
871
+ });
872
+ });
873
+ }
874
+ static async resumeClaudeSession(sessionId) {
875
+ return new Promise((resolve, reject) => {
876
+ requestAPI('claude-sessions/resume', {
877
+ method: 'POST',
878
+ body: JSON.stringify({ session_id: sessionId })
879
+ })
880
+ .then(() => {
881
+ resolve();
882
+ })
883
+ .catch(reason => {
884
+ console.error(`Failed to resume Claude session.\n${reason}`);
885
+ reject(reason);
886
+ });
887
+ });
888
+ }
889
+ static async emitTelemetryEvent(event) {
890
+ const assistantMode = this.config.isInClaudeCodeMode
891
+ ? AssistantMode.Claude
892
+ : AssistantMode.Default;
893
+ event.data = {
894
+ ...(event.data || {}),
895
+ assistantMode
896
+ };
897
+ return new Promise((resolve, reject) => {
898
+ requestAPI('emit-telemetry-event', {
899
+ method: 'POST',
900
+ body: JSON.stringify(event)
901
+ })
902
+ .then(async (data) => {
903
+ resolve();
904
+ })
905
+ .catch(reason => {
906
+ console.error(`Failed to emit telemetry event.\n${reason}`);
907
+ reject(reason);
908
+ });
909
+ });
910
+ }
911
+ }
912
+ _a = NBIAPI;
913
+ NBIAPI._loginStatus = GitHubCopilotLoginStatus.NotLoggedIn;
914
+ NBIAPI._deviceVerificationInfo = {
915
+ verificationURI: '',
916
+ userCode: ''
917
+ };
918
+ NBIAPI._messageReceived = new Signal(_a);
919
+ NBIAPI.config = new NBIConfig();
920
+ NBIAPI.configChanged = _a.config.changed;
921
+ NBIAPI.githubLoginStatusChanged = new Signal(_a);
922
+ NBIAPI.skillsReloaded = new Signal(_a);
923
+ // Emits each time the Claude agent sends its 20s keepalive (#252 follow-up).
924
+ // The chat sidebar uses it to drive the "Generating" indicator's pulse
925
+ // and to swap to a "server may be slow" copy when the gap stretches.
926
+ NBIAPI.claudeCodeHeartbeat = new Signal(_a);
927
+ export { NBIAPI };