@jupyterlite/ai 0.9.0-a3 → 0.9.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 (50) hide show
  1. package/README.md +20 -89
  2. package/lib/agent.d.ts +10 -4
  3. package/lib/agent.js +30 -17
  4. package/lib/chat-model.d.ts +6 -0
  5. package/lib/chat-model.js +144 -17
  6. package/lib/completion/completion-provider.js +1 -13
  7. package/lib/components/completion-status.d.ts +20 -0
  8. package/lib/components/completion-status.js +51 -0
  9. package/lib/components/index.d.ts +1 -0
  10. package/lib/components/index.js +1 -0
  11. package/lib/components/model-select.js +1 -2
  12. package/lib/diff-manager.d.ts +25 -0
  13. package/lib/diff-manager.js +60 -0
  14. package/lib/icons.d.ts +0 -1
  15. package/lib/icons.js +2 -6
  16. package/lib/index.d.ts +2 -2
  17. package/lib/index.js +54 -23
  18. package/lib/models/settings-model.d.ts +4 -0
  19. package/lib/models/settings-model.js +24 -2
  20. package/lib/providers/built-in-providers.d.ts +0 -4
  21. package/lib/providers/built-in-providers.js +17 -23
  22. package/lib/tokens.d.ts +74 -0
  23. package/lib/tokens.js +4 -0
  24. package/lib/tools/commands.js +36 -35
  25. package/lib/tools/file.d.ts +10 -1
  26. package/lib/tools/file.js +235 -146
  27. package/lib/tools/notebook.d.ts +2 -3
  28. package/lib/tools/notebook.js +11 -11
  29. package/lib/widgets/ai-settings.js +78 -13
  30. package/lib/widgets/provider-config-dialog.js +15 -8
  31. package/package.json +5 -3
  32. package/schema/settings-model.json +25 -0
  33. package/src/agent.ts +35 -20
  34. package/src/chat-model.ts +182 -19
  35. package/src/completion/completion-provider.ts +1 -14
  36. package/src/components/completion-status.tsx +79 -0
  37. package/src/components/index.ts +1 -0
  38. package/src/components/model-select.tsx +0 -3
  39. package/src/diff-manager.ts +81 -0
  40. package/src/icons.ts +2 -7
  41. package/src/index.ts +74 -24
  42. package/src/models/settings-model.ts +28 -2
  43. package/src/providers/built-in-providers.ts +17 -24
  44. package/src/tokens.ts +78 -0
  45. package/src/tools/commands.ts +45 -40
  46. package/src/tools/file.ts +295 -164
  47. package/src/tools/notebook.ts +13 -14
  48. package/src/widgets/ai-settings.tsx +184 -35
  49. package/src/widgets/provider-config-dialog.tsx +43 -16
  50. package/style/base.css +14 -0
package/src/index.ts CHANGED
@@ -26,6 +26,8 @@ import { ICompletionProviderManager } from '@jupyterlab/completer';
26
26
 
27
27
  import { IDocumentManager } from '@jupyterlab/docmanager';
28
28
 
29
+ import { IEditorTracker } from '@jupyterlab/fileeditor';
30
+
29
31
  import { INotebookTracker } from '@jupyterlab/notebook';
30
32
 
31
33
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
@@ -34,6 +36,8 @@ import { IKernelSpecManager, KernelSpec } from '@jupyterlab/services';
34
36
 
35
37
  import { ISettingRegistry } from '@jupyterlab/settingregistry';
36
38
 
39
+ import { IStatusBar } from '@jupyterlab/statusbar';
40
+
37
41
  import {
38
42
  settingsIcon,
39
43
  Toolbar,
@@ -61,7 +65,8 @@ import {
61
65
  IToolRegistry,
62
66
  SECRETS_NAMESPACE,
63
67
  IAISettingsModel,
64
- IChatModelRegistry
68
+ IChatModelRegistry,
69
+ IDiffManager
65
70
  } from './tokens';
66
71
 
67
72
  import {
@@ -69,7 +74,6 @@ import {
69
74
  googleProvider,
70
75
  mistralProvider,
71
76
  openaiProvider,
72
- ollamaProvider,
73
77
  genericProvider
74
78
  } from './providers/built-in-providers';
75
79
 
@@ -80,11 +84,14 @@ import {
80
84
  createModelSelectItem,
81
85
  createToolSelectItem,
82
86
  stopItem,
87
+ CompletionStatusWidget,
83
88
  TokenUsageWidget
84
89
  } from './components';
85
90
 
86
91
  import { AISettingsModel } from './models/settings-model';
87
92
 
93
+ import { DiffManager } from './diff-manager';
94
+
88
95
  import { ToolRegistry } from './tools/tool-registry';
89
96
 
90
97
  import {
@@ -102,10 +109,12 @@ import {
102
109
  import {
103
110
  createCopyFileTool,
104
111
  createDeleteFileTool,
112
+ createGetFileInfoTool,
105
113
  createNavigateToDirectoryTool,
106
114
  createNewFileTool,
107
115
  createOpenFileTool,
108
- createRenameFileTool
116
+ createRenameFileTool,
117
+ createSetFileContentTool
109
118
  } from './tools/file';
110
119
 
111
120
  import {
@@ -182,19 +191,6 @@ const openaiProviderPlugin: JupyterFrontEndPlugin<void> = {
182
191
  }
183
192
  };
184
193
 
185
- /**
186
- * Ollama provider plugin
187
- */
188
- const ollamaProviderPlugin: JupyterFrontEndPlugin<void> = {
189
- id: '@jupyterlite/ai:ollama-provider',
190
- description: 'Register Ollama provider',
191
- autoStart: true,
192
- requires: [IProviderRegistry],
193
- activate: (app: JupyterFrontEnd, providerRegistry: IProviderRegistry) => {
194
- providerRegistry.registerProvider(ollamaProvider);
195
- }
196
- };
197
-
198
194
  /**
199
195
  * Generic provider plugin
200
196
  */
@@ -691,6 +687,7 @@ const agentManagerFactory: JupyterFrontEndPlugin<AgentManagerFactory> =
691
687
  });
692
688
  settingsWidget.id = 'jupyterlite-ai-settings';
693
689
  settingsWidget.title.icon = settingsIcon;
690
+ settingsWidget.title.iconClass = 'jp-ai-settings-icon';
694
691
 
695
692
  // Build the completion provider
696
693
  if (completionManager) {
@@ -716,6 +713,7 @@ const agentManagerFactory: JupyterFrontEndPlugin<AgentManagerFactory> =
716
713
  label: 'AI Settings',
717
714
  caption: 'Configure AI providers and behavior',
718
715
  icon: settingsIcon,
716
+ iconClass: 'jp-ai-settings-icon',
719
717
  execute: () => {
720
718
  // Check if the widget already exists in shell
721
719
  let widget = Array.from(app.shell.widgets('main')).find(
@@ -763,21 +761,42 @@ const settingsModel: JupyterFrontEndPlugin<AISettingsModel> = {
763
761
  }
764
762
  };
765
763
 
764
+ /**
765
+ * Diff manager plugin
766
+ */
767
+ const diffManager: JupyterFrontEndPlugin<IDiffManager> = {
768
+ id: '@jupyterlite/ai:diff-manager',
769
+ description: 'Provide the diff manager for notebook cell diffs',
770
+ autoStart: true,
771
+ provides: IDiffManager,
772
+ requires: [IAISettingsModel],
773
+ activate: (
774
+ app: JupyterFrontEnd,
775
+ settingsModel: AISettingsModel
776
+ ): IDiffManager => {
777
+ return new DiffManager({
778
+ commands: app.commands,
779
+ settingsModel
780
+ });
781
+ }
782
+ };
783
+
766
784
  const toolRegistry: JupyterFrontEndPlugin<IToolRegistry> = {
767
785
  id: '@jupyterlite/ai:tool-registry',
768
786
  description: 'Provide the AI tool registry',
769
787
  autoStart: true,
770
788
  requires: [IAISettingsModel, IDocumentManager, IKernelSpecManager],
771
- optional: [INotebookTracker],
789
+ optional: [INotebookTracker, IDiffManager, IEditorTracker],
772
790
  provides: IToolRegistry,
773
791
  activate: (
774
792
  app: JupyterFrontEnd,
775
793
  settingsModel: AISettingsModel,
776
794
  docManager: IDocumentManager,
777
795
  kernelSpecManager: KernelSpec.IManager,
778
- notebookTracker?: INotebookTracker
796
+ notebookTracker?: INotebookTracker,
797
+ diffManager?: IDiffManager,
798
+ editorTracker?: IEditorTracker
779
799
  ) => {
780
- const { commands } = app;
781
800
  const toolRegistry = new ToolRegistry();
782
801
 
783
802
  const notebookCreationTool = createNotebookCreationTool(
@@ -795,8 +814,8 @@ const toolRegistry: JupyterFrontEndPlugin<IToolRegistry> = {
795
814
  const getCellInfoTool = createGetCellInfoTool(docManager, notebookTracker);
796
815
  const setCellContentTool = createSetCellContentTool(
797
816
  docManager,
798
- commands,
799
- notebookTracker
817
+ notebookTracker,
818
+ diffManager
800
819
  );
801
820
  const runCellTool = createRunCellTool(docManager, notebookTracker);
802
821
  const deleteCellTool = createDeleteCellTool(docManager, notebookTracker);
@@ -825,6 +844,11 @@ const toolRegistry: JupyterFrontEndPlugin<IToolRegistry> = {
825
844
  const renameFileTool = createRenameFileTool(docManager);
826
845
  const copyFileTool = createCopyFileTool(docManager);
827
846
  const navigateToDirectoryTool = createNavigateToDirectoryTool(app.commands);
847
+ const getFileInfoTool = createGetFileInfoTool(docManager, editorTracker);
848
+ const setFileContentTool = createSetFileContentTool(
849
+ docManager,
850
+ diffManager
851
+ );
828
852
 
829
853
  toolRegistry.add('create_file', newFileTool);
830
854
  toolRegistry.add('open_file', openFileTool);
@@ -832,6 +856,8 @@ const toolRegistry: JupyterFrontEndPlugin<IToolRegistry> = {
832
856
  toolRegistry.add('rename_file', renameFileTool);
833
857
  toolRegistry.add('copy_file', copyFileTool);
834
858
  toolRegistry.add('navigate_to_directory', navigateToDirectoryTool);
859
+ toolRegistry.add('get_file_info', getFileInfoTool);
860
+ toolRegistry.add('set_file_content', setFileContentTool);
835
861
 
836
862
  // Add command operation tools
837
863
  const discoverCommandsTool = createDiscoverCommandsTool(app.commands);
@@ -852,7 +878,7 @@ const toolRegistry: JupyterFrontEndPlugin<IToolRegistry> = {
852
878
  */
853
879
  const inputToolbarFactory: JupyterFrontEndPlugin<IInputToolbarRegistryFactory> =
854
880
  {
855
- id: 'labai:input-toolbar-factory',
881
+ id: '@jupyterlite/ai:input-toolbar-factory',
856
882
  description: 'The input toolbar registry plugin.',
857
883
  autoStart: true,
858
884
  provides: IInputToolbarRegistryFactory,
@@ -895,20 +921,44 @@ const inputToolbarFactory: JupyterFrontEndPlugin<IInputToolbarRegistryFactory> =
895
921
  }
896
922
  };
897
923
 
924
+ const completionStatus: JupyterFrontEndPlugin<void> = {
925
+ id: '@jupyterlite/ai:completion-status',
926
+ description: 'The completion status displayed in the status bar',
927
+ autoStart: true,
928
+ requires: [IAISettingsModel],
929
+ optional: [IStatusBar],
930
+ activate: (
931
+ app: JupyterFrontEnd,
932
+ settingsModel: AISettingsModel,
933
+ statusBar: IStatusBar | null
934
+ ) => {
935
+ if (!statusBar) {
936
+ return;
937
+ }
938
+ const item = new CompletionStatusWidget({ settingsModel });
939
+ statusBar?.registerStatusItem('completionState', {
940
+ item,
941
+ align: 'right',
942
+ rank: 10
943
+ });
944
+ }
945
+ };
946
+
898
947
  export default [
899
948
  providerRegistryPlugin,
900
949
  anthropicProviderPlugin,
901
950
  googleProviderPlugin,
902
951
  mistralProviderPlugin,
903
952
  openaiProviderPlugin,
904
- ollamaProviderPlugin,
905
953
  genericProviderPlugin,
906
954
  settingsModel,
955
+ diffManager,
907
956
  chatModelRegistry,
908
957
  plugin,
909
958
  toolRegistry,
910
959
  agentManagerFactory,
911
- inputToolbarFactory
960
+ inputToolbarFactory,
961
+ completionStatus
912
962
  ];
913
963
 
914
964
  // Export extension points for other extensions to use
@@ -48,6 +48,7 @@ export interface IAIConfig {
48
48
  contextAwareness: boolean;
49
49
  codeExecution: boolean;
50
50
  systemPrompt: string;
51
+ completionSystemPrompt: string;
51
52
  toolsEnabled: boolean;
52
53
  // Chat behavior settings
53
54
  sendWithShiftEnter: boolean;
@@ -55,6 +56,10 @@ export interface IAIConfig {
55
56
  showTokenUsage: boolean;
56
57
  // Commands that require approval before execution
57
58
  commandsRequiringApproval: string[];
59
+ // Diff display settings
60
+ showCellDiff: boolean;
61
+ showFileDiff: boolean;
62
+ diffDisplayMode: 'split' | 'unified';
58
63
  }
59
64
 
60
65
  export class AISettingsModel extends VDomModel {
@@ -70,6 +75,9 @@ export class AISettingsModel extends VDomModel {
70
75
  toolsEnabled: true,
71
76
  sendWithShiftEnter: false,
72
77
  showTokenUsage: false,
78
+ showCellDiff: true,
79
+ showFileDiff: true,
80
+ diffDisplayMode: 'split',
73
81
  commandsRequiringApproval: [
74
82
  'notebook:restart-run-all',
75
83
  'notebook:run-cell',
@@ -154,7 +162,18 @@ When users request complex tasks that require multiple steps (like "create a not
154
162
 
155
163
  Always think through multi-step tasks and use tools to fully complete the user's request rather than stopping after just one action.
156
164
 
157
- Ready to help you build something great! What are you working on?`
165
+ Ready to help you build something great! What are you working on?`,
166
+ // Completion system prompt - also defined in schema/settings-model.json
167
+ // This serves as a fallback if settings fail to load or are not available
168
+ completionSystemPrompt: `You are an AI code completion assistant. Complete the given code fragment with appropriate code.
169
+ Rules:
170
+ - Return only the completion text, no explanations or comments
171
+ - Do not include code block markers (\`\`\` or similar)
172
+ - Make completions contextually relevant to the surrounding code and notebook context
173
+ - Follow the language-specific conventions and style guidelines for the detected programming language
174
+ - Keep completions concise but functional
175
+ - Do not repeat the existing code that comes before the cursor
176
+ - Use variables, imports, functions, and other definitions from previous notebook cells when relevant`
158
177
  };
159
178
 
160
179
  private _settingRegistry: ISettingRegistry;
@@ -222,7 +241,7 @@ Ready to help you build something great! What are you working on?`
222
241
  }
223
242
  return this._config.activeCompleterProvider
224
243
  ? this.getProvider(this._config.activeCompleterProvider)
225
- : this.getDefaultProvider();
244
+ : undefined;
226
245
  }
227
246
 
228
247
  async addProvider(
@@ -292,6 +311,11 @@ Ready to help you build something great! What are you working on?`
292
311
  }
293
312
 
294
313
  Object.assign(provider, updates);
314
+ Object.keys(provider).forEach(key => {
315
+ if (key !== 'id' && updates[key] === undefined) {
316
+ delete provider[key];
317
+ }
318
+ });
295
319
  await this.saveSetting('providers', this._config.providers);
296
320
  }
297
321
 
@@ -397,6 +421,8 @@ Ready to help you build something great! What are you working on?`
397
421
  // Only save the specific setting that changed
398
422
  if (value !== undefined) {
399
423
  await this._settings.set(key, value as any);
424
+ } else {
425
+ await this._settings.remove(key);
400
426
  }
401
427
  }
402
428
  } catch (error) {
@@ -2,7 +2,7 @@ import { createAnthropic } from '@ai-sdk/anthropic';
2
2
  import { createGoogleGenerativeAI } from '@ai-sdk/google';
3
3
  import { createMistral } from '@ai-sdk/mistral';
4
4
  import { createOpenAI } from '@ai-sdk/openai';
5
- import { createOllama } from 'ollama-ai-provider-v2';
5
+ import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
6
6
 
7
7
  import type { IProviderInfo } from '../tokens';
8
8
  import type { IModelOptions } from './models';
@@ -17,6 +17,8 @@ export const anthropicProvider: IProviderInfo = {
17
17
  defaultModels: [
18
18
  'claude-sonnet-4-5',
19
19
  'claude-sonnet-4-5-20250929',
20
+ 'claude-haiku-4-5',
21
+ 'claude-haiku-4-5-20251001',
20
22
  'claude-opus-4-1',
21
23
  'claude-opus-4-0',
22
24
  'claude-sonnet-4-0',
@@ -200,26 +202,6 @@ export const openaiProvider: IProviderInfo = {
200
202
  }
201
203
  };
202
204
 
203
- /**
204
- * Ollama provider
205
- */
206
- export const ollamaProvider: IProviderInfo = {
207
- id: 'ollama',
208
- name: 'Ollama',
209
- apiKeyRequirement: 'none',
210
- defaultModels: [],
211
- supportsBaseURL: true,
212
- supportsHeaders: true,
213
- factory: (options: IModelOptions) => {
214
- const ollama = createOllama({
215
- baseURL: options.baseURL || 'http://localhost:11434/api',
216
- ...(options.headers && { headers: options.headers })
217
- });
218
- const modelName = options.model || 'phi3';
219
- return ollama(modelName);
220
- }
221
- };
222
-
223
205
  /**
224
206
  * Generic OpenAI-compatible provider
225
207
  */
@@ -232,13 +214,24 @@ export const genericProvider: IProviderInfo = {
232
214
  supportsHeaders: true,
233
215
  supportsToolCalling: true,
234
216
  description: 'Uses /chat/completions endpoint',
217
+ baseUrls: [
218
+ {
219
+ url: 'http://localhost:4000',
220
+ description: 'Default for local LiteLLM server'
221
+ },
222
+ {
223
+ url: 'http://localhost:11434/v1',
224
+ description: 'Default for local Ollama server'
225
+ }
226
+ ],
235
227
  factory: (options: IModelOptions) => {
236
- const openai = createOpenAI({
228
+ const openaiCompatible = createOpenAICompatible({
229
+ name: options.provider,
237
230
  apiKey: options.apiKey || 'dummy',
238
- ...(options.baseURL && { baseURL: options.baseURL }),
231
+ baseURL: options.baseURL ?? '',
239
232
  ...(options.headers && { headers: options.headers })
240
233
  });
241
234
  const modelName = options.model || 'gpt-4o';
242
- return openai(modelName);
235
+ return openaiCompatible(modelName);
243
236
  }
244
237
  };
package/src/tokens.ts CHANGED
@@ -159,6 +159,11 @@ export interface IProviderInfo {
159
159
  */
160
160
  description?: string;
161
161
 
162
+ /**
163
+ * Optional URL suggestions
164
+ */
165
+ baseUrls?: { url: string; description?: string }[];
166
+
162
167
  /**
163
168
  * Factory function for creating language models
164
169
  */
@@ -250,3 +255,76 @@ export interface IChatModelRegistry {
250
255
  export const IChatModelRegistry = new Token<IChatModelRegistry>(
251
256
  '@jupyterlite/ai:chat-model-registry'
252
257
  );
258
+
259
+ /**
260
+ * Parameters for showing cell diff
261
+ */
262
+ export interface IShowCellDiffParams {
263
+ /**
264
+ * Original cell content
265
+ */
266
+ original: string;
267
+ /**
268
+ * Modified cell content
269
+ */
270
+ modified: string;
271
+ /**
272
+ * Optional cell ID
273
+ */
274
+ cellId?: string;
275
+ /**
276
+ * Whether to show action buttons in the diff view
277
+ */
278
+ showActionButtons?: boolean;
279
+ /**
280
+ * Whether to open the diff view
281
+ */
282
+ openDiff?: boolean;
283
+ /**
284
+ * Optional path to the notebook
285
+ */
286
+ notebookPath?: string;
287
+ }
288
+
289
+ /**
290
+ * Parameters for showing file diff
291
+ */
292
+ export interface IShowFileDiffParams {
293
+ /**
294
+ * Original file content
295
+ */
296
+ original: string;
297
+ /**
298
+ * Modified file content
299
+ */
300
+ modified: string;
301
+ /**
302
+ * Optional file path
303
+ */
304
+ filePath?: string;
305
+ /**
306
+ * Whether to show action buttons in the diff view
307
+ */
308
+ showActionButtons?: boolean;
309
+ }
310
+
311
+ /**
312
+ * Interface for managing diff operations
313
+ */
314
+ export interface IDiffManager {
315
+ /**
316
+ * Show diff between original and modified cell content
317
+ */
318
+ showCellDiff(params: IShowCellDiffParams): Promise<void>;
319
+ /**
320
+ * Show diff between original and modified file content
321
+ */
322
+ showFileDiff(params: IShowFileDiffParams): Promise<void>;
323
+ }
324
+
325
+ /**
326
+ * Token for the diff manager.
327
+ */
328
+ export const IDiffManager = new Token<IDiffManager>(
329
+ '@jupyterlite/ai:diff-manager'
330
+ );
@@ -20,51 +20,56 @@ export function createDiscoverCommandsTool(commands: CommandRegistry): ITool {
20
20
  .nullable()
21
21
  .describe('Optional search query to filter commands')
22
22
  }),
23
- execute: async () => {
24
- try {
25
- const commandList: Array<{
26
- id: string;
27
- label?: string;
28
- caption?: string;
29
- description?: string;
30
- args?: any;
31
- }> = [];
23
+ execute: async (input: { query?: string | null }) => {
24
+ const { query } = input;
25
+ const commandList: Array<{
26
+ id: string;
27
+ label?: string;
28
+ caption?: string;
29
+ description?: string;
30
+ args?: any;
31
+ }> = [];
32
32
 
33
- // Get all command IDs
34
- const commandIds = commands.listCommands();
33
+ // Get all command IDs
34
+ const commandIds = commands.listCommands();
35
35
 
36
- for (const id of commandIds) {
37
- try {
38
- // Get command metadata using various CommandRegistry methods
39
- const description = await commands.describedBy(id);
40
- const label = commands.label(id);
41
- const caption = commands.caption(id);
42
- const usage = commands.usage(id);
36
+ for (const id of commandIds) {
37
+ // Get command metadata using various CommandRegistry methods
38
+ const description = await commands.describedBy(id);
39
+ const label = commands.label(id);
40
+ const caption = commands.caption(id);
41
+ const usage = commands.usage(id);
42
+
43
+ const command = {
44
+ id,
45
+ label: label || undefined,
46
+ caption: caption || undefined,
47
+ description: usage || undefined,
48
+ args: description?.args || undefined
49
+ };
43
50
 
44
- commandList.push({
45
- id,
46
- label: label || undefined,
47
- caption: caption || undefined,
48
- description: usage || undefined,
49
- args: description?.args || undefined
50
- });
51
- } catch (error) {
52
- // Some commands might not have descriptions, skip them
53
- commandList.push({ id });
51
+ // Filter by query if provided
52
+ if (query) {
53
+ const searchTerm = query.toLowerCase();
54
+ const matchesQuery =
55
+ id.toLowerCase().includes(searchTerm) ||
56
+ label?.toLowerCase().includes(searchTerm) ||
57
+ caption?.toLowerCase().includes(searchTerm) ||
58
+ usage?.toLowerCase().includes(searchTerm);
59
+
60
+ if (matchesQuery) {
61
+ commandList.push(command);
54
62
  }
63
+ } else {
64
+ commandList.push(command);
55
65
  }
56
-
57
- return {
58
- success: true,
59
- commandCount: commandList.length,
60
- commands: commandList
61
- };
62
- } catch (error) {
63
- return {
64
- success: false,
65
- error: `Failed to discover commands: ${error instanceof Error ? error.message : String(error)}`
66
- };
67
66
  }
67
+
68
+ return {
69
+ success: true,
70
+ commandCount: commandList.length,
71
+ commands: commandList
72
+ };
68
73
  }
69
74
  });
70
75
  }
@@ -87,7 +92,7 @@ export function createExecuteCommandTool(
87
92
  .optional()
88
93
  .describe('Optional arguments to pass to the command')
89
94
  }),
90
- needsApproval: async (_context, { commandId }) => {
95
+ needsApproval: async (context, { commandId }) => {
91
96
  // Use configurable list of commands requiring approval
92
97
  const commandsRequiringApproval =
93
98
  settingsModel.config.commandsRequiringApproval;