@jupyterlite/ai 0.11.1 → 0.12.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 (48) hide show
  1. package/lib/agent.d.ts +37 -5
  2. package/lib/agent.js +142 -96
  3. package/lib/chat-commands/clear.d.ts +8 -0
  4. package/lib/chat-commands/clear.js +30 -0
  5. package/lib/chat-commands/index.d.ts +2 -0
  6. package/lib/chat-commands/index.js +2 -0
  7. package/lib/chat-commands/skills.d.ts +19 -0
  8. package/lib/chat-commands/skills.js +57 -0
  9. package/lib/chat-model.d.ts +8 -0
  10. package/lib/chat-model.js +35 -3
  11. package/lib/index.d.ts +3 -3
  12. package/lib/index.js +187 -10
  13. package/lib/models/settings-model.d.ts +1 -0
  14. package/lib/models/settings-model.js +61 -14
  15. package/lib/providers/built-in-providers.js +5 -7
  16. package/lib/skills/index.d.ts +4 -0
  17. package/lib/skills/index.js +7 -0
  18. package/lib/skills/parse-skill.d.ts +25 -0
  19. package/lib/skills/parse-skill.js +69 -0
  20. package/lib/skills/skill-loader.d.ts +25 -0
  21. package/lib/skills/skill-loader.js +133 -0
  22. package/lib/skills/skill-registry.d.ts +31 -0
  23. package/lib/skills/skill-registry.js +100 -0
  24. package/lib/skills/types.d.ts +29 -0
  25. package/lib/skills/types.js +5 -0
  26. package/lib/tokens.d.ts +33 -0
  27. package/lib/tokens.js +5 -0
  28. package/lib/tools/skills.d.ts +9 -0
  29. package/lib/tools/skills.js +73 -0
  30. package/lib/widgets/ai-settings.js +33 -1
  31. package/package.json +10 -9
  32. package/schema/settings-model.json +8 -1
  33. package/src/agent.ts +198 -102
  34. package/src/chat-commands/clear.ts +46 -0
  35. package/src/chat-commands/index.ts +2 -0
  36. package/src/chat-commands/skills.ts +87 -0
  37. package/src/chat-model.ts +48 -10
  38. package/src/index.ts +242 -5
  39. package/src/models/settings-model.ts +64 -15
  40. package/src/providers/built-in-providers.ts +5 -7
  41. package/src/skills/index.ts +14 -0
  42. package/src/skills/parse-skill.ts +91 -0
  43. package/src/skills/skill-loader.ts +175 -0
  44. package/src/skills/skill-registry.ts +137 -0
  45. package/src/skills/types.ts +37 -0
  46. package/src/tokens.ts +56 -0
  47. package/src/tools/skills.ts +84 -0
  48. package/src/widgets/ai-settings.tsx +75 -0
package/src/chat-model.ts CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  IActiveCellManager,
4
4
  IAttachment,
5
5
  IChatContext,
6
- IChatMessage,
6
+ IMessageContent,
7
7
  INewMessage,
8
8
  IUser
9
9
  } from '@jupyter/chat';
@@ -160,7 +160,8 @@ export class AIChatModel extends AbstractChatModel {
160
160
  messages: this.messages,
161
161
  stopStreaming: () => this.stopStreaming(),
162
162
  clearMessages: () => this.clearMessages(),
163
- agentManager: this._agentManager
163
+ agentManager: this._agentManager,
164
+ addSystemMessage: (body: string) => this.addSystemMessage(body)
164
165
  };
165
166
  }
166
167
 
@@ -180,13 +181,34 @@ export class AIChatModel extends AbstractChatModel {
180
181
  this._agentManager.clearHistory();
181
182
  };
182
183
 
184
+ /**
185
+ * Adds a non-user message to the chat (used by chat commands).
186
+ */
187
+ addSystemMessage(body: string): void {
188
+ const message: IMessageContent = {
189
+ body,
190
+ sender: this._getAIUser(),
191
+ id: UUID.uuid4(),
192
+ time: Date.now() / 1000,
193
+ type: 'msg',
194
+ raw_time: false
195
+ };
196
+ this.messageAdded(message);
197
+ }
198
+
183
199
  /**
184
200
  * Sends a message to the AI and generates a response.
185
201
  * @param message The user message to send
186
202
  */
187
203
  async sendMessage(message: INewMessage): Promise<void> {
204
+ const hasBody = message.body.trim().length > 0;
205
+ const hasAttachments = this.input.attachments.length > 0;
206
+ if (!hasBody && !hasAttachments) {
207
+ return;
208
+ }
209
+
188
210
  // Add user message to chat
189
- const userMessage: IChatMessage = {
211
+ const userMessage: IMessageContent = {
190
212
  body: message.body,
191
213
  sender: this.user || { username: 'user', display_name: 'User' },
192
214
  id: UUID.uuid4(),
@@ -199,7 +221,7 @@ export class AIChatModel extends AbstractChatModel {
199
221
 
200
222
  // Check if we have valid configuration
201
223
  if (!this._agentManager.hasValidConfig()) {
202
- const errorMessage: IChatMessage = {
224
+ const errorMessage: IMessageContent = {
203
225
  body: 'Please configure your AI settings first. Open the AI Settings to set your API key and model.',
204
226
  sender: this._getAIUser(),
205
227
  id: UUID.uuid4(),
@@ -230,7 +252,7 @@ export class AIChatModel extends AbstractChatModel {
230
252
 
231
253
  await this._agentManager.generateResponse(enhancedMessage);
232
254
  } catch (error) {
233
- const errorMessage: IChatMessage = {
255
+ const errorMessage: IMessageContent = {
234
256
  body: `Error generating AI response: ${(error as Error).message}`,
235
257
  sender: this._getAIUser(),
236
258
  id: UUID.uuid4(),
@@ -304,7 +326,7 @@ export class AIChatModel extends AbstractChatModel {
304
326
  * @param event Event containing the message start data
305
327
  */
306
328
  private _handleMessageStart(event: IAgentEvent<'message_start'>): void {
307
- const aiMessage: IChatMessage = {
329
+ const aiMessage: IMessageContent = {
308
330
  body: '',
309
331
  sender: this._getAIUser(),
310
332
  id: event.data.messageId,
@@ -366,6 +388,19 @@ export class AIChatModel extends AbstractChatModel {
366
388
  return `query: "${parsedInput.query}"`;
367
389
  }
368
390
  break;
391
+ case 'discover_skills':
392
+ if (parsedInput.query) {
393
+ return `query: "${parsedInput.query}"`;
394
+ }
395
+ break;
396
+ case 'load_skill':
397
+ if (parsedInput.name) {
398
+ if (parsedInput.resource) {
399
+ return `${parsedInput.name} (${parsedInput.resource})`;
400
+ }
401
+ return parsedInput.name;
402
+ }
403
+ break;
369
404
  }
370
405
  } catch {
371
406
  // If parsing fails, return empty string
@@ -396,7 +431,7 @@ export class AIChatModel extends AbstractChatModel {
396
431
 
397
432
  this._toolContexts.set(event.data.callId, context);
398
433
 
399
- const toolCallMessage: IChatMessage = {
434
+ const toolCallMessage: IMessageContent = {
400
435
  body: Private.buildToolCallHtml({
401
436
  toolName: context.toolName,
402
437
  input: context.input,
@@ -496,8 +531,7 @@ export class AIChatModel extends AbstractChatModel {
496
531
  }
497
532
 
498
533
  context.status = status;
499
- this.messageAdded({
500
- ...existingMessage,
534
+ existingMessage.update({
501
535
  body: Private.buildToolCallHtml({
502
536
  toolName: context.toolName,
503
537
  input: context.input,
@@ -793,7 +827,7 @@ export class AIChatModel extends AbstractChatModel {
793
827
  private _user: IUser;
794
828
  private _toolContexts: Map<string, IToolExecutionContext> = new Map();
795
829
  private _agentManager: AgentManager;
796
- private _currentStreamingMessage: IChatMessage | null = null;
830
+ private _currentStreamingMessage: IMessageContent | null = null;
797
831
  private _nameChanged = new Signal<AIChatModel, string>(this);
798
832
  private _trans: TranslationBundle;
799
833
  }
@@ -989,6 +1023,10 @@ export namespace AIChatModel {
989
1023
  * The clear messages callback.
990
1024
  */
991
1025
  clearMessages: () => void;
1026
+ /**
1027
+ * Adds an assistant/system message to the chat.
1028
+ */
1029
+ addSystemMessage: (body: string) => void;
992
1030
  /**
993
1031
  * The agent manager of the chat.
994
1032
  */
package/src/index.ts CHANGED
@@ -9,8 +9,10 @@ import {
9
9
  ActiveCellManager,
10
10
  AttachmentOpenerRegistry,
11
11
  chatIcon,
12
+ ChatCommandRegistry,
12
13
  ChatWidget,
13
14
  IAttachmentOpenerRegistry,
15
+ IChatCommandRegistry,
14
16
  IInputToolbarRegistryFactory,
15
17
  InputToolbarRegistry,
16
18
  MultiChatPanel
@@ -34,6 +36,8 @@ import { ISettingRegistry } from '@jupyterlab/settingregistry';
34
36
 
35
37
  import { IStatusBar } from '@jupyterlab/statusbar';
36
38
 
39
+ import { PathExt } from '@jupyterlab/coreutils';
40
+
37
41
  import {
38
42
  ITranslator,
39
43
  nullTranslator,
@@ -49,11 +53,15 @@ import {
49
53
  import { ISecretsManager, SecretsManager } from 'jupyter-secrets-manager';
50
54
 
51
55
  import { PromiseDelegate, UUID } from '@lumino/coreutils';
56
+ import { DisposableSet } from '@lumino/disposable';
52
57
 
53
58
  import { AgentManagerFactory } from './agent';
54
59
 
55
60
  import { AIChatModel } from './chat-model';
56
61
 
62
+ import { ClearCommandProvider } from './chat-commands/clear';
63
+ import { SkillsCommandProvider } from './chat-commands/skills';
64
+
57
65
  import { ProviderRegistry } from './providers/provider-registry';
58
66
 
59
67
  import { ApprovalButtons } from './approval-buttons';
@@ -65,6 +73,7 @@ import {
65
73
  IAgentManagerFactory,
66
74
  IProviderRegistry,
67
75
  IToolRegistry,
76
+ ISkillRegistry,
68
77
  SECRETS_NAMESPACE,
69
78
  IAISettingsModel,
70
79
  IChatModelRegistry,
@@ -92,6 +101,8 @@ import {
92
101
 
93
102
  import { AISettingsModel } from './models/settings-model';
94
103
 
104
+ import { loadSkillsFromPaths, SkillRegistry } from './skills';
105
+
95
106
  import { DiffManager } from './diff-manager';
96
107
 
97
108
  import { ToolRegistry } from './tools/tool-registry';
@@ -100,6 +111,7 @@ import {
100
111
  createDiscoverCommandsTool,
101
112
  createExecuteCommandTool
102
113
  } from './tools/commands';
114
+ import { createDiscoverSkillsTool, createLoadSkillTool } from './tools/skills';
103
115
 
104
116
  import { AISettingsWidget } from './widgets/ai-settings';
105
117
 
@@ -183,6 +195,54 @@ const genericProviderPlugin: JupyterFrontEndPlugin<void> = {
183
195
  }
184
196
  };
185
197
 
198
+ /**
199
+ * Chat command registry plugin.
200
+ */
201
+ const chatCommandRegistryPlugin: JupyterFrontEndPlugin<IChatCommandRegistry> = {
202
+ id: '@jupyterlite/ai:chat-command-registry',
203
+ description: 'Provide the chat command registry for JupyterLite AI.',
204
+ autoStart: true,
205
+ provides: IChatCommandRegistry,
206
+ activate: () => {
207
+ return new ChatCommandRegistry();
208
+ }
209
+ };
210
+
211
+ /**
212
+ * Clear chat command plugin.
213
+ */
214
+ const clearCommandPlugin: JupyterFrontEndPlugin<void> = {
215
+ id: '@jupyterlite/ai:clear-command',
216
+ description: 'Register the /clear chat command.',
217
+ autoStart: true,
218
+ requires: [IChatCommandRegistry],
219
+ activate: (app, registry: IChatCommandRegistry) => {
220
+ registry.addProvider(new ClearCommandProvider());
221
+ }
222
+ };
223
+
224
+ /**
225
+ * Skills chat command plugin.
226
+ */
227
+ const skillsCommandPlugin: JupyterFrontEndPlugin<void> = {
228
+ id: '@jupyterlite/ai:skills-command',
229
+ description: 'Register the /skills chat command.',
230
+ autoStart: true,
231
+ requires: [IChatCommandRegistry, ISkillRegistry],
232
+ activate: (
233
+ app,
234
+ registry: IChatCommandRegistry,
235
+ skillRegistry: ISkillRegistry
236
+ ) => {
237
+ registry.addProvider(
238
+ new SkillsCommandProvider({
239
+ skillRegistry,
240
+ commands: app.commands
241
+ })
242
+ );
243
+ }
244
+ };
245
+
186
246
  /**
187
247
  * The chat model registry.
188
248
  */
@@ -226,7 +286,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
226
286
  IRenderMimeRegistry,
227
287
  IInputToolbarRegistryFactory,
228
288
  IChatModelRegistry,
229
- IAISettingsModel
289
+ IAISettingsModel,
290
+ IChatCommandRegistry
230
291
  ],
231
292
  optional: [
232
293
  IThemeManager,
@@ -241,6 +302,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
241
302
  inputToolbarFactory: IInputToolbarRegistryFactory,
242
303
  modelRegistry: IChatModelRegistry,
243
304
  settingsModel: AISettingsModel,
305
+ chatCommandRegistry: IChatCommandRegistry,
244
306
  themeManager?: IThemeManager,
245
307
  restorer?: ILayoutRestorer,
246
308
  labShell?: ILabShell,
@@ -275,6 +337,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
275
337
  themeManager: themeManager ?? null,
276
338
  inputToolbarFactory,
277
339
  attachmentOpenerRegistry,
340
+ chatCommandRegistry,
278
341
  createModel: async (name?: string) => {
279
342
  const model = modelRegistry.createModel(name);
280
343
  return { model };
@@ -400,6 +463,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
400
463
  attachmentOpenerRegistry,
401
464
  inputToolbarFactory,
402
465
  settingsModel,
466
+ chatCommandRegistry,
403
467
  tracker,
404
468
  modelRegistry,
405
469
  trans,
@@ -416,6 +480,7 @@ function registerCommands(
416
480
  attachmentOpenerRegistry: IAttachmentOpenerRegistry,
417
481
  inputToolbarFactory: IInputToolbarRegistryFactory,
418
482
  settingsModel: AISettingsModel,
483
+ chatCommandRegistry: IChatCommandRegistry,
419
484
  tracker: WidgetTracker<MainAreaChat | ChatWidget>,
420
485
  modelRegistry: IChatModelRegistry,
421
486
  trans: TranslationBundle,
@@ -481,7 +546,8 @@ function registerCommands(
481
546
  rmRegistry,
482
547
  themeManager: themeManager ?? null,
483
548
  inputToolbarRegistry: inputToolbarFactory.create(),
484
- attachmentOpenerRegistry
549
+ attachmentOpenerRegistry,
550
+ chatCommandRegistry
485
551
  });
486
552
  const widget = new MainAreaChat({
487
553
  content,
@@ -666,6 +732,7 @@ const agentManagerFactory: JupyterFrontEndPlugin<AgentManagerFactory> =
666
732
  provides: IAgentManagerFactory,
667
733
  requires: [IAISettingsModel, IProviderRegistry],
668
734
  optional: [
735
+ ISkillRegistry,
669
736
  ICommandPalette,
670
737
  ICompletionProviderManager,
671
738
  ILayoutRestorer,
@@ -677,7 +744,8 @@ const agentManagerFactory: JupyterFrontEndPlugin<AgentManagerFactory> =
677
744
  app: JupyterFrontEnd,
678
745
  settingsModel: AISettingsModel,
679
746
  providerRegistry: IProviderRegistry,
680
- palette: ICommandPalette,
747
+ skillRegistry?: ISkillRegistry,
748
+ palette?: ICommandPalette,
681
749
  completionManager?: ICompletionProviderManager,
682
750
  restorer?: ILayoutRestorer,
683
751
  secretsManager?: ISecretsManager,
@@ -687,6 +755,7 @@ const agentManagerFactory: JupyterFrontEndPlugin<AgentManagerFactory> =
687
755
  const trans = (translator ?? nullTranslator).load('jupyterlite_ai');
688
756
  const agentManagerFactory = new AgentManagerFactory({
689
757
  settingsModel,
758
+ skillRegistry,
690
759
  secretsManager,
691
760
  token
692
761
  });
@@ -797,13 +866,31 @@ const diffManager: JupyterFrontEndPlugin<IDiffManager> = {
797
866
  }
798
867
  };
799
868
 
869
+ /**
870
+ * Skill registry plugin
871
+ */
872
+ const skillRegistryPlugin: JupyterFrontEndPlugin<ISkillRegistry> = {
873
+ id: '@jupyterlite/ai:skill-registry',
874
+ description: 'Provide the skill registry',
875
+ autoStart: true,
876
+ provides: ISkillRegistry,
877
+ activate: () => {
878
+ return new SkillRegistry();
879
+ }
880
+ };
881
+
800
882
  const toolRegistry: JupyterFrontEndPlugin<IToolRegistry> = {
801
883
  id: '@jupyterlite/ai:tool-registry',
802
884
  description: 'Provide the AI tool registry',
803
885
  autoStart: true,
804
886
  requires: [IAISettingsModel],
887
+ optional: [ISkillRegistry],
805
888
  provides: IToolRegistry,
806
- activate: (app: JupyterFrontEnd, settingsModel: AISettingsModel) => {
889
+ activate: (
890
+ app: JupyterFrontEnd,
891
+ settingsModel: AISettingsModel,
892
+ skillRegistry?: ISkillRegistry
893
+ ) => {
807
894
  const toolRegistry = new ToolRegistry();
808
895
 
809
896
  // Add command operation tools
@@ -815,6 +902,13 @@ const toolRegistry: JupyterFrontEndPlugin<IToolRegistry> = {
815
902
 
816
903
  toolRegistry.add('discover_commands', discoverCommandsTool);
817
904
  toolRegistry.add('execute_command', executeCommandTool);
905
+ if (skillRegistry) {
906
+ toolRegistry.add(
907
+ 'discover_skills',
908
+ createDiscoverSkillsTool(skillRegistry)
909
+ );
910
+ toolRegistry.add('load_skill', createLoadSkillTool(skillRegistry));
911
+ }
818
912
 
819
913
  return toolRegistry;
820
914
  }
@@ -900,6 +994,144 @@ const completionStatus: JupyterFrontEndPlugin<void> = {
900
994
  }
901
995
  };
902
996
 
997
+ /**
998
+ * Skills plugin: discovers and registers agent skills from the filesystem.
999
+ */
1000
+ const skillsPlugin: JupyterFrontEndPlugin<void> = {
1001
+ id: '@jupyterlite/ai:skills',
1002
+ description: 'Discover and register agent skills',
1003
+ autoStart: true,
1004
+ requires: [IAISettingsModel, IDocumentManager, ISkillRegistry],
1005
+ optional: [ICommandPalette, ITranslator],
1006
+ activate: async (
1007
+ app: JupyterFrontEnd,
1008
+ settingsModel: AISettingsModel,
1009
+ docManager: IDocumentManager,
1010
+ skillRegistry: ISkillRegistry,
1011
+ palette?: ICommandPalette,
1012
+ translator?: ITranslator
1013
+ ) => {
1014
+ const trans = (translator ?? nullTranslator).load('jupyterlite_ai');
1015
+ const validateResourcePath = (resourcePath: string): string | null => {
1016
+ if (resourcePath.startsWith('/')) {
1017
+ return null;
1018
+ }
1019
+
1020
+ const normalized = PathExt.normalize(resourcePath);
1021
+ if (normalized.startsWith('..') || normalized === '') {
1022
+ return null;
1023
+ }
1024
+
1025
+ return normalized;
1026
+ };
1027
+
1028
+ let currentSkillsPaths = settingsModel.config.skillsPaths;
1029
+ let currentSkillDisposables = new DisposableSet();
1030
+
1031
+ const loadAndRegister = async () => {
1032
+ const skillsPaths = settingsModel.config.skillsPaths;
1033
+ const skills = await loadSkillsFromPaths(
1034
+ docManager.services.contents,
1035
+ skillsPaths
1036
+ );
1037
+
1038
+ const registrations = skills.map(skill => ({
1039
+ name: skill.name,
1040
+ description: skill.description,
1041
+ instructions: skill.instructions,
1042
+ resources: skill.resources,
1043
+ loadResource: async (resource: string) => {
1044
+ const validatedPath = validateResourcePath(resource);
1045
+ if (validatedPath === null) {
1046
+ return {
1047
+ name: skill.name,
1048
+ resource,
1049
+ error: 'Invalid resource path: path traversal not allowed'
1050
+ };
1051
+ }
1052
+
1053
+ if (!skill.resources.includes(validatedPath)) {
1054
+ return {
1055
+ name: skill.name,
1056
+ resource,
1057
+ error: `Resource not found: ${resource}`
1058
+ };
1059
+ }
1060
+
1061
+ const resourcePath = `${skill.path}/${validatedPath}`;
1062
+ try {
1063
+ const fileModel = await docManager.services.contents.get(
1064
+ resourcePath,
1065
+ {
1066
+ content: true
1067
+ }
1068
+ );
1069
+ if (typeof fileModel.content !== 'string') {
1070
+ return {
1071
+ name: skill.name,
1072
+ resource,
1073
+ error: 'Resource content is not a string'
1074
+ };
1075
+ }
1076
+ return {
1077
+ name: skill.name,
1078
+ resource,
1079
+ content: fileModel.content
1080
+ };
1081
+ } catch (error) {
1082
+ return {
1083
+ name: skill.name,
1084
+ resource,
1085
+ error: `Failed to read resource: ${error}`
1086
+ };
1087
+ }
1088
+ }
1089
+ }));
1090
+
1091
+ currentSkillDisposables.dispose();
1092
+ currentSkillDisposables = new DisposableSet();
1093
+ for (const registration of registrations) {
1094
+ currentSkillDisposables.add(skillRegistry.registerSkill(registration));
1095
+ }
1096
+ };
1097
+
1098
+ app.commands.addCommand(CommandIds.refreshSkills, {
1099
+ label: trans.__('Refresh Agents Skills'),
1100
+ caption: trans.__(
1101
+ 'Re-scan the agents skills directory and update the registry'
1102
+ ),
1103
+ execute: async () => {
1104
+ await loadAndRegister();
1105
+ }
1106
+ });
1107
+
1108
+ if (palette) {
1109
+ palette.addItem({
1110
+ command: CommandIds.refreshSkills,
1111
+ category: trans.__('AI Assistant')
1112
+ });
1113
+ }
1114
+
1115
+ loadAndRegister().catch(error =>
1116
+ console.warn('Failed to load skills on activation:', error)
1117
+ );
1118
+
1119
+ settingsModel.stateChanged.connect(() => {
1120
+ const newPaths = settingsModel.config.skillsPaths;
1121
+ if (
1122
+ newPaths.length === currentSkillsPaths.length &&
1123
+ newPaths.every((p, i) => p === currentSkillsPaths[i])
1124
+ ) {
1125
+ return;
1126
+ }
1127
+ currentSkillsPaths = newPaths;
1128
+ loadAndRegister().catch(error =>
1129
+ console.warn('Failed to reload skills:', error)
1130
+ );
1131
+ });
1132
+ }
1133
+ };
1134
+
903
1135
  export default [
904
1136
  providerRegistryPlugin,
905
1137
  anthropicProviderPlugin,
@@ -909,12 +1141,17 @@ export default [
909
1141
  genericProviderPlugin,
910
1142
  settingsModel,
911
1143
  diffManager,
1144
+ chatCommandRegistryPlugin,
1145
+ clearCommandPlugin,
1146
+ skillRegistryPlugin,
1147
+ skillsCommandPlugin,
912
1148
  chatModelRegistry,
913
1149
  plugin,
914
1150
  toolRegistry,
915
1151
  agentManagerFactory,
916
1152
  inputToolbarFactory,
917
- completionStatus
1153
+ completionStatus,
1154
+ skillsPlugin
918
1155
  ];
919
1156
 
920
1157
  // Export extension points for other extensions to use
@@ -60,6 +60,8 @@ export interface IAIConfig {
60
60
  showCellDiff: boolean;
61
61
  showFileDiff: boolean;
62
62
  diffDisplayMode: 'split' | 'unified';
63
+ // Paths to directories containing agent skills
64
+ skillsPaths: string[];
63
65
  }
64
66
 
65
67
  export class AISettingsModel extends VDomModel {
@@ -78,6 +80,7 @@ export class AISettingsModel extends VDomModel {
78
80
  showCellDiff: true,
79
81
  showFileDiff: true,
80
82
  diffDisplayMode: 'split',
83
+ skillsPaths: ['.agents/skills', '_agents/skills'],
81
84
  commandsRequiringApproval: [
82
85
  'notebook:restart-run-all',
83
86
  'notebook:run-cell',
@@ -113,7 +116,7 @@ You're designed to be a capable partner for data science, research, and developm
113
116
 
114
117
  **⚡ Kernel Management:**
115
118
  - Start new kernels with specified language or kernel name
116
- - Execute code directly in running kernels without creating cells
119
+ - Execute code directly in a kernel using jupyterlab-ai-commands execution commands (not console), without creating cells
117
120
  - List running kernels and monitor their status
118
121
  - Manage kernel lifecycle (start, monitor, shutdown)
119
122
 
@@ -130,18 +133,32 @@ You're designed to be a capable partner for data science, research, and developm
130
133
  - Help with both quick fixes and long-term project planning
131
134
 
132
135
  ## How You Work
133
- You can actively interact with the user's JupyterLab environment using specialized tools. When asked to perform actions, you can:
134
- - Execute operations directly in notebooks
135
- - Create and modify files as needed
136
- - Run code and analyze results
137
- - Make systematic changes across multiple files
136
+ You interact with the user's JupyterLab environment primarily through the command system:
137
+ - Use 'discover_commands' to find available JupyterLab commands
138
+ - Use 'execute_command' to perform operations
139
+ - For file and notebook operations, use commands from the jupyterlab-ai-commands extension (prefixed with 'jupyterlab-ai-commands:')
140
+ - These commands provide comprehensive file and notebook manipulation: create, read, edit files/notebooks, manage cells, run code, etc.
141
+ - You can make systematic changes across multiple files and perform complex multi-step operations
142
+ - Skills are available via the skills tools: discover_skills (list) and load_skill (load instructions/resources)
143
+
144
+ ## Tool & Skill Use Policy
145
+ - When tools or skills are available and the task requires actions or environment-specific facts, use them instead of guessing
146
+ - Never guess command IDs. Always use discover_commands with a relevant query before execute_command, unless you already discovered the command earlier in this conversation
147
+ - If a preloaded skills snapshot is provided in the system prompt, use it instead of calling discover_skills to list skills
148
+ - Only call discover_skills if the user explicitly asks for the latest list or you need to verify a skill not in the snapshot
149
+ - When a skill is relevant, call load_skill with the skill name to load instructions; if it returns a non-empty resources array, load each listed resource with load_skill before proceeding
150
+ - If you're unsure how to perform a request, discover relevant commands (discover_commands with task keywords)
151
+ - Use a relevant skill even when the user doesn't explicitly mention it
152
+ - Prefer the single most relevant tool or skill; if multiple could apply, ask a brief clarifying question
153
+ - Ask for missing required inputs before calling a tool or skill
154
+ - Before calling a tool or skill, briefly state why you're calling it
138
155
 
139
156
  ## Code Execution Strategy
140
157
  When asked to run code or perform computations, choose the most appropriate approach:
141
- - **For quick computations or one-off code execution**: Use kernel commands to start a kernel and execute code directly, without creating notebook files. This is ideal for calculations, data lookups, or testing code snippets.
158
+ - **For quick computations or one-off code execution**: Use the kernel execution commands from jupyterlab-ai-commands to run code directly (no notebook/console). Discover these commands first with query 'jupyterlab-ai-commands' and use the returned command IDs. This is ideal for calculations, data lookups, or testing code snippets.
142
159
  - **For work that should be saved**: Create or use notebooks when the user needs a persistent record of their work, wants to iterate on code, or is building something they'll return to later.
143
160
 
144
- This means if the user asks you to "calculate the factorial of 100" or "check what library version is installed", run that directly in a kernel rather than creating a new notebook file.
161
+ This means if the user asks you to "calculate the factorial of 100" or "check what library version is installed", run that directly with the jupyterlab-ai-commands kernel execution command rather than creating a new notebook file.
145
162
 
146
163
  ## Your Approach
147
164
  - **Context-aware**: You understand the user is working in a data science/research environment
@@ -150,6 +167,23 @@ This means if the user asks you to "calculate the factorial of 100" or "check wh
150
167
  - **Collaborative**: You are a pair programming partner, not just a code generator
151
168
 
152
169
  ## Communication Style & Agent Behavior
170
+ IMPORTANT: Follow this message flow pattern for better user experience:
171
+
172
+ 1. FIRST: Explain what you're going to do and your approach
173
+ 2. THEN: Execute tools (these will show automatically with step numbers)
174
+ 3. FINALLY: Provide a concise summary of what was accomplished
175
+
176
+ Example flow:
177
+ - "I'll help you create a notebook with example cells. Let me first create the file structure, then add Python and Markdown cells."
178
+ - [Tool executions happen with automatic step display]
179
+ - "Successfully created your notebook with 3 cells: a title, code example, and visualization cell."
180
+
181
+ Guidelines:
182
+ - Start responses with your plan/approach before tool execution
183
+ - Let the system handle tool execution display (don't duplicate details)
184
+ - End with a brief summary of accomplishments
185
+ - Use natural, conversational tone throughout
186
+
153
187
  - **Conversational**: You maintain a friendly, natural conversation flow throughout the interaction
154
188
  - **Progress Updates**: You write brief progress messages between tool uses that appear directly in the conversation
155
189
  - **No Filler**: You avoid empty acknowledgments like "Sounds good!" or "Okay, I will..." - you get straight to work
@@ -168,13 +202,28 @@ This means if the user asks you to "calculate the factorial of 100" or "check wh
168
202
  - You keep users informed of progress while staying focused on the task
169
203
 
170
204
  ## Multi-Step Task Handling
171
- When users request complex tasks that require multiple steps (like "create a notebook with example cells"), you use tools in sequence to accomplish the complete task. For example:
172
- - First use create_notebook to create the notebook
173
- - Then use add_code_cell or add_markdown_cell to add cells
174
- - Use set_cell_content to add content to cells as needed
175
- - Use run_cell to execute code when appropriate
176
-
177
- Always think through multi-step tasks and use tools to fully complete the user's request rather than stopping after just one action.
205
+ When users request complex tasks, you use the command system to accomplish them:
206
+ - For file and notebook operations, use discover_commands with query 'jupyterlab-ai-commands' to find the curated set of AI commands (~17 commands)
207
+ - For other JupyterLab operations (terminal, launcher, UI), use specific keywords like 'terminal', 'launcher', etc.
208
+ - IMPORTANT: Always use 'jupyterlab-ai-commands' as the query for file/notebook tasks - this returns a focused set instead of 100+ generic commands
209
+ - For example, to create a notebook with cells:
210
+ 1. discover_commands with query 'jupyterlab-ai-commands' to find available file/notebook commands
211
+ 2. execute_command with 'jupyterlab-ai-commands:create-notebook' and required arguments
212
+ 3. execute_command with 'jupyterlab-ai-commands:add-cell' multiple times to add cells
213
+ 4. execute_command with 'jupyterlab-ai-commands:set-cell-content' to add content to cells
214
+ 5. execute_command with 'jupyterlab-ai-commands:run-cell' when appropriate
215
+
216
+ ## Kernel Preference for Notebooks and Consoles
217
+ When creating notebooks or consoles for a specific programming language, use the 'kernelPreference' argument:
218
+ Only create consoles when the user explicitly asks for one; otherwise prefer the jupyterlab-ai-commands kernel execution commands for running code.
219
+ - To specify by language: { "kernelPreference": { "language": "python" } } or { "kernelPreference": { "language": "julia" } }
220
+ - To specify by kernel name: { "kernelPreference": { "name": "python3" } } or { "kernelPreference": { "name": "julia-1.10" } }
221
+ - Example: execute_command with commandId="notebook:create-new" and args={ "kernelPreference": { "language": "python" } }
222
+ - Example: execute_command with commandId="console:create" and args={ "kernelPreference": { "name": "python3" } }
223
+ - Common kernel names: "python3" (Python), "julia-1.10" (Julia), "ir" (R), "xpython" (xeus-python)
224
+ - If unsure of exact kernel name, prefer using "language" which will match any kernel supporting that language
225
+
226
+ Always think through multi-step tasks and use commands to fully complete the user's request rather than stopping after just one action.
178
227
 
179
228
  You are ready to help users build something great!`,
180
229
  // Completion system prompt - also defined in schema/settings-model.json