wave-agent-sdk 0.11.7 → 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 (63) hide show
  1. package/builtin/skills/settings/MODELS.md +67 -0
  2. package/builtin/skills/settings/SKILL.md +13 -7
  3. package/dist/agent.d.ts +10 -0
  4. package/dist/agent.d.ts.map +1 -1
  5. package/dist/agent.js +16 -1
  6. package/dist/managers/aiManager.d.ts.map +1 -1
  7. package/dist/managers/aiManager.js +1 -0
  8. package/dist/managers/liveConfigManager.d.ts +2 -0
  9. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  10. package/dist/managers/liveConfigManager.js +3 -0
  11. package/dist/managers/messageManager.d.ts +5 -1
  12. package/dist/managers/messageManager.d.ts.map +1 -1
  13. package/dist/managers/messageManager.js +28 -1
  14. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  15. package/dist/managers/slashCommandManager.js +38 -41
  16. package/dist/managers/toolManager.d.ts +1 -0
  17. package/dist/managers/toolManager.d.ts.map +1 -1
  18. package/dist/services/aiService.d.ts.map +1 -1
  19. package/dist/services/aiService.js +30 -5
  20. package/dist/services/configurationService.d.ts +8 -0
  21. package/dist/services/configurationService.d.ts.map +1 -1
  22. package/dist/services/configurationService.js +67 -1
  23. package/dist/tools/skillTool.d.ts.map +1 -1
  24. package/dist/tools/skillTool.js +4 -1
  25. package/dist/tools/types.d.ts +1 -0
  26. package/dist/tools/types.d.ts.map +1 -1
  27. package/dist/types/agent.d.ts +3 -0
  28. package/dist/types/agent.d.ts.map +1 -1
  29. package/dist/types/config.d.ts +1 -0
  30. package/dist/types/config.d.ts.map +1 -1
  31. package/dist/types/configuration.d.ts +3 -0
  32. package/dist/types/configuration.d.ts.map +1 -1
  33. package/dist/types/messaging.d.ts +11 -2
  34. package/dist/types/messaging.d.ts.map +1 -1
  35. package/dist/utils/configValidator.d.ts +4 -5
  36. package/dist/utils/configValidator.d.ts.map +1 -1
  37. package/dist/utils/configValidator.js +4 -4
  38. package/dist/utils/containerSetup.d.ts.map +1 -1
  39. package/dist/utils/containerSetup.js +7 -1
  40. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  41. package/dist/utils/convertMessagesForAPI.js +16 -1
  42. package/dist/utils/messageOperations.d.ts +28 -3
  43. package/dist/utils/messageOperations.d.ts.map +1 -1
  44. package/dist/utils/messageOperations.js +79 -7
  45. package/package.json +1 -1
  46. package/src/agent.ts +18 -4
  47. package/src/managers/aiManager.ts +1 -0
  48. package/src/managers/liveConfigManager.ts +5 -1
  49. package/src/managers/messageManager.ts +40 -0
  50. package/src/managers/slashCommandManager.ts +39 -46
  51. package/src/managers/toolManager.ts +1 -0
  52. package/src/services/aiService.ts +58 -5
  53. package/src/services/configurationService.ts +81 -1
  54. package/src/tools/skillTool.ts +12 -2
  55. package/src/tools/types.ts +1 -0
  56. package/src/types/agent.ts +3 -0
  57. package/src/types/config.ts +1 -0
  58. package/src/types/configuration.ts +3 -0
  59. package/src/types/messaging.ts +12 -1
  60. package/src/utils/configValidator.ts +6 -4
  61. package/src/utils/containerSetup.ts +7 -1
  62. package/src/utils/convertMessagesForAPI.ts +17 -1
  63. package/src/utils/messageOperations.ts +111 -8
@@ -29,6 +29,7 @@ import { logger } from "../utils/globalLogger.js";
29
29
 
30
30
  export interface LiveConfigManagerOptions {
31
31
  workdir: string;
32
+ onReload?: (config: WaveConfiguration) => void;
32
33
  }
33
34
 
34
35
  export class LiveConfigManager {
@@ -48,7 +49,7 @@ export class LiveConfigManager {
48
49
 
49
50
  constructor(
50
51
  private container: Container,
51
- options: LiveConfigManagerOptions,
52
+ private options: LiveConfigManagerOptions,
52
53
  ) {
53
54
  this.workdir = options.workdir;
54
55
  this.fileWatcher = new FileWatcherService(logger);
@@ -272,6 +273,9 @@ export class LiveConfigManager {
272
273
  );
273
274
  }
274
275
 
276
+ // Trigger reload callback
277
+ this.options.onReload?.(this.currentConfiguration);
278
+
275
279
  return this.currentConfiguration;
276
280
  } catch (error) {
277
281
  const errorMessage = `Configuration reload failed with exception: ${(error as Error).message}`;
@@ -7,9 +7,13 @@ import {
7
7
  addBangMessage,
8
8
  updateBangInMessage,
9
9
  completeBangInMessage,
10
+ addSlashMessageToMessages,
11
+ updateSlashBlockInMessage,
10
12
  removeLastUserMessage,
11
13
  addToolBlockToMessageInMessages,
12
14
  UserMessageParams,
15
+ AddSlashParams,
16
+ UpdateSlashParams,
13
17
  type AgentToolBlockUpdateParams,
14
18
  generateMessageId,
15
19
  } from "../utils/messageOperations.js";
@@ -50,6 +54,13 @@ export interface MessageManagerCallbacks {
50
54
  onAddBangMessage?: (command: string) => void;
51
55
  onUpdateBangMessage?: (command: string, output: string) => void;
52
56
  onCompleteBangMessage?: (command: string, exitCode: number) => void;
57
+ // Slash callback
58
+ onAddSlashMessage?: (
59
+ params: import("../utils/messageOperations.js").AddSlashParams,
60
+ ) => void;
61
+ onUpdateSlashBlock?: (
62
+ params: import("../utils/messageOperations.js").UpdateSlashParams,
63
+ ) => void;
53
64
  onInfoBlockAdded?: (content: string) => void;
54
65
  // Rewind callbacks
55
66
  onShowRewind?: () => void;
@@ -583,6 +594,35 @@ export class MessageManager {
583
594
  this.callbacks.onCompleteBangMessage?.(command, exitCode);
584
595
  }
585
596
 
597
+ // Slash related message operations
598
+ public addSlashMessage(params: Omit<AddSlashParams, "messages">): string {
599
+ const id = params.id || generateMessageId();
600
+ const updatedMessages = addSlashMessageToMessages({
601
+ messages: this.messages,
602
+ ...params,
603
+ id,
604
+ });
605
+ this.setMessages(updatedMessages);
606
+ this.callbacks.onAddSlashMessage?.({
607
+ messages: updatedMessages,
608
+ ...params,
609
+ id,
610
+ });
611
+ return id;
612
+ }
613
+
614
+ public updateSlashBlock(params: Omit<UpdateSlashParams, "messages">): void {
615
+ const updatedMessages = updateSlashBlockInMessage({
616
+ messages: this.messages,
617
+ ...params,
618
+ });
619
+ this.setMessages(updatedMessages);
620
+ this.callbacks.onUpdateSlashBlock?.({
621
+ messages: updatedMessages,
622
+ ...params,
623
+ });
624
+ }
625
+
586
626
  /**
587
627
  * Rebuild usage array from messages containing usage metadata
588
628
  * Called during session restoration to reconstruct usage tracking
@@ -171,18 +171,21 @@ export class SlashCommandManager {
171
171
  args,
172
172
  });
173
173
 
174
- const originalInput = args
175
- ? `/${skill.name} ${args}`
176
- : `/${skill.name}`;
177
-
178
- // 2. Add user message immediately
179
- const messageId = this.messageManager.addUserMessage({
180
- content: originalInput,
181
- customCommandContent: prepared.content,
174
+ // 2. Add slash message immediately
175
+ const messageId = this.messageManager.addSlashMessage({
176
+ command: skill.name,
177
+ args,
178
+ content: prepared.content,
182
179
  });
183
180
 
184
181
  if (!prepared.skill) {
185
182
  // If skill not found or invalid, we're done (error message already in prepared.content)
183
+ this.messageManager.updateSlashBlock({
184
+ command: skill.name,
185
+ messageId,
186
+ stage: "error",
187
+ error: prepared.content,
188
+ });
186
189
  return;
187
190
  }
188
191
 
@@ -200,20 +203,6 @@ export class SlashCommandManager {
200
203
  );
201
204
  }
202
205
 
203
- // Add a ToolBlock to the initial command message for progress tracking
204
- const toolBlockId = this.messageManager.addToolBlockToMessage(
205
- messageId,
206
- {
207
- name: subagentType,
208
- parameters: JSON.stringify({
209
- description: skill.description,
210
- prompt: prepared.content,
211
- subagent_type: subagentType,
212
- }),
213
- stage: "running",
214
- },
215
- );
216
-
217
206
  try {
218
207
  const instance = await this.subagentManager.createInstance(
219
208
  config,
@@ -225,7 +214,7 @@ export class SlashCommandManager {
225
214
  },
226
215
  false,
227
216
  () => {
228
- // Update the tool block with progress
217
+ // Update the slash block with progress
229
218
  const subagent = this.subagentManager.getInstance(
230
219
  instance.subagentId,
231
220
  );
@@ -248,8 +237,8 @@ export class SlashCommandManager {
248
237
 
249
238
  shortResult += summary;
250
239
 
251
- this.messageManager.updateToolBlock({
252
- id: toolBlockId,
240
+ this.messageManager.updateSlashBlock({
241
+ command: skill.name,
253
242
  messageId,
254
243
  shortResult,
255
244
  });
@@ -264,25 +253,25 @@ export class SlashCommandManager {
264
253
  signal,
265
254
  );
266
255
 
267
- // Update the ToolBlock with final result
268
- this.messageManager.updateToolBlock({
269
- id: toolBlockId,
256
+ // Update the SlashBlock with final result
257
+ this.messageManager.updateSlashBlock({
258
+ command: skill.name,
270
259
  messageId,
271
260
  result,
272
- success: true,
273
- stage: "end",
261
+ stage: "success",
274
262
  });
275
263
  } finally {
276
264
  this.subagentManager.cleanupInstance(instance.subagentId);
277
265
  }
278
266
  } catch (error) {
279
- // Update the ToolBlock with error
280
- this.messageManager.updateToolBlock({
281
- id: toolBlockId,
267
+ // Update the SlashBlock with error
268
+ const isAborted =
269
+ error instanceof Error && error.name === "AbortError";
270
+ this.messageManager.updateSlashBlock({
271
+ command: skill.name,
282
272
  messageId,
283
- success: false,
273
+ stage: isAborted ? "aborted" : "error",
284
274
  error: error instanceof Error ? error.message : String(error),
285
- stage: "end",
286
275
  });
287
276
  throw error; // Re-throw to be caught by outer catch for logging/error block
288
277
  }
@@ -296,8 +285,11 @@ export class SlashCommandManager {
296
285
  });
297
286
 
298
287
  // 4. Update the message with final content
299
- this.messageManager.updateUserMessage(messageId, {
300
- customCommandContent: result.content,
288
+ this.messageManager.updateSlashBlock({
289
+ command: skill.name,
290
+ messageId,
291
+ content: result.content,
292
+ stage: "success",
301
293
  });
302
294
 
303
295
  // 5. Trigger AI response
@@ -498,13 +490,11 @@ export class SlashCommandManager {
498
490
  args?: string,
499
491
  ): Promise<void> {
500
492
  try {
501
- // Add custom command message immediately to show the command being executed
502
- const originalInput = args
503
- ? `/${commandName} ${args}`
504
- : `/${commandName}`;
505
- const messageId = this.messageManager.addUserMessage({
506
- content: originalInput,
507
- customCommandContent: content, // Initial content with bash placeholders
493
+ // Add slash command message immediately to show the command being executed
494
+ const messageId = this.messageManager.addSlashMessage({
495
+ command: commandName,
496
+ args,
497
+ content, // Initial content with bash placeholders
508
498
  });
509
499
 
510
500
  // Parse bash commands from the content
@@ -521,8 +511,11 @@ export class SlashCommandManager {
521
511
  }
522
512
 
523
513
  // Update the message with final content
524
- this.messageManager.updateUserMessage(messageId, {
525
- customCommandContent: finalContent,
514
+ this.messageManager.updateSlashBlock({
515
+ command: commandName,
516
+ messageId,
517
+ content: finalContent,
518
+ stage: "success",
526
519
  });
527
520
 
528
521
  // Execute the AI conversation with custom configuration
@@ -284,6 +284,7 @@ class ToolManager {
284
284
  availableSubagents?: SubagentConfiguration[];
285
285
  availableSkills?: SkillMetadata[];
286
286
  workdir?: string;
287
+ isSubagent?: boolean;
287
288
  }): ChatCompletionFunctionTool[] {
288
289
  const permissionManager =
289
290
  this.container.get<PermissionManager>("PermissionManager");
@@ -135,10 +135,12 @@ function getModelConfig(
135
135
  ...baseConfig,
136
136
  };
137
137
 
138
- // Configuration rules for specific models
139
- if (modelName.includes("gpt-5")) {
140
- // gpt-5 models should not have temperature field
141
- delete config.temperature;
138
+ // Handle parameter exclusion: if a parameter is explicitly set to null, remove it.
139
+ // This allows users to "unset" default parameters like temperature for models that don't support them.
140
+ for (const key in config) {
141
+ if (config[key as keyof OpenAIModelConfig] === null) {
142
+ delete config[key as keyof OpenAIModelConfig];
143
+ }
142
144
  }
143
145
 
144
146
  return config;
@@ -261,9 +263,21 @@ export async function callAgent(
261
263
  }
262
264
 
263
265
  // Get model configuration - use injected modelConfig with optional override
266
+ const {
267
+ model: _model,
268
+ fastModel: _fastModel,
269
+ maxTokens: _maxTokens,
270
+ permissionMode: _permissionMode,
271
+ ...extraParams
272
+ } = modelConfig;
273
+ void _model;
274
+ void _fastModel;
275
+ void _maxTokens;
276
+ void _permissionMode;
277
+
264
278
  const openaiModelConfig = getModelConfig(model || modelConfig.model, {
265
- temperature: 0,
266
279
  max_tokens: resolvedMaxTokens,
280
+ ...extraParams,
267
281
  });
268
282
 
269
283
  // Determine if streaming is needed
@@ -780,9 +794,22 @@ export async function compressMessages(
780
794
  });
781
795
 
782
796
  // Get model configuration - use injected agent model
797
+ const {
798
+ model: _model,
799
+ fastModel: _fastModel,
800
+ maxTokens: _maxTokens,
801
+ permissionMode: _permissionMode,
802
+ ...extraParams
803
+ } = modelConfig;
804
+ void _model;
805
+ void _fastModel;
806
+ void _maxTokens;
807
+ void _permissionMode;
808
+
783
809
  const openaiModelConfig = getModelConfig(options.model || modelConfig.model, {
784
810
  temperature: 0.1,
785
811
  max_tokens: 8192,
812
+ ...extraParams,
786
813
  });
787
814
 
788
815
  try {
@@ -878,9 +905,22 @@ export async function processWebContent(
878
905
  });
879
906
 
880
907
  // Get model configuration - use injected agent model
908
+ const {
909
+ model: _model,
910
+ fastModel: _fastModel,
911
+ maxTokens: _maxTokens,
912
+ permissionMode: _permissionMode,
913
+ ...extraParams
914
+ } = modelConfig;
915
+ void _model;
916
+ void _fastModel;
917
+ void _maxTokens;
918
+ void _permissionMode;
919
+
881
920
  const openaiModelConfig = getModelConfig(options.model || modelConfig.model, {
882
921
  temperature: 0.1,
883
922
  max_tokens: 4096,
923
+ ...extraParams,
884
924
  });
885
925
 
886
926
  try {
@@ -972,9 +1012,22 @@ export async function btw(options: BtwOptions): Promise<BtwResult> {
972
1012
  });
973
1013
 
974
1014
  // Get model configuration - use injected agent model
1015
+ const {
1016
+ model: _model,
1017
+ fastModel: _fastModel,
1018
+ maxTokens: _maxTokens,
1019
+ permissionMode: _permissionMode,
1020
+ ...extraParams
1021
+ } = modelConfig;
1022
+ void _model;
1023
+ void _fastModel;
1024
+ void _maxTokens;
1025
+ void _permissionMode;
1026
+
975
1027
  const openaiModelConfig = getModelConfig(options.model || modelConfig.model, {
976
1028
  temperature: 0.1,
977
1029
  max_tokens: 4096,
1030
+ ...extraParams,
978
1031
  });
979
1032
 
980
1033
  try {
@@ -275,6 +275,23 @@ export class ConfigurationService {
275
275
  result.errors.push("autoMemoryEnabled configuration must be a boolean");
276
276
  }
277
277
 
278
+ // Validate models if present
279
+ if (config.models !== undefined) {
280
+ if (typeof config.models !== "object" || config.models === null) {
281
+ result.isValid = false;
282
+ result.errors.push("models configuration must be an object");
283
+ } else {
284
+ for (const [modelName, modelConfig] of Object.entries(config.models)) {
285
+ if (typeof modelConfig !== "object" || modelConfig === null) {
286
+ result.isValid = false;
287
+ result.errors.push(
288
+ `Configuration for model '${modelName}' must be an object`,
289
+ );
290
+ }
291
+ }
292
+ }
293
+ }
294
+
278
295
  return result;
279
296
  }
280
297
 
@@ -461,12 +478,25 @@ export class ConfigurationService {
461
478
  // Resolve max output tokens
462
479
  const resolvedMaxTokens = this.resolveMaxOutputTokens(maxTokens);
463
480
 
464
- return {
481
+ const baseConfig: ModelConfig = {
465
482
  model: resolvedAgentModel,
466
483
  fastModel: resolvedFastModel,
467
484
  maxTokens: resolvedMaxTokens,
468
485
  permissionMode: permissionMode ?? this.options.permissionMode,
469
486
  };
487
+
488
+ // Merge model-specific settings from configuration
489
+ const modelSpecificConfig =
490
+ this.currentConfiguration?.models?.[resolvedAgentModel];
491
+
492
+ if (modelSpecificConfig) {
493
+ return {
494
+ ...baseConfig,
495
+ ...modelSpecificConfig,
496
+ };
497
+ }
498
+
499
+ return baseConfig;
470
500
  }
471
501
 
472
502
  /**
@@ -578,6 +608,40 @@ export class ConfigurationService {
578
608
  return DEFAULT_WAVE_MAX_OUTPUT_TOKENS;
579
609
  }
580
610
 
611
+ /**
612
+ * Set the active model in the session
613
+ */
614
+ setModel(model: string): void {
615
+ this.options.model = model;
616
+ }
617
+
618
+ /**
619
+ * Get all configured models from settings.json and defaults
620
+ */
621
+ getConfiguredModels(): string[] {
622
+ const DEFAULT_AGENT_MODEL = "gemini-3-flash";
623
+ const models = new Set<string>();
624
+
625
+ // Add default model
626
+ models.add(DEFAULT_AGENT_MODEL);
627
+
628
+ // Add current model from options or environment
629
+ const currentModel =
630
+ this.options.model || this.env.WAVE_MODEL || process.env.WAVE_MODEL;
631
+ if (currentModel) {
632
+ models.add(currentModel);
633
+ }
634
+
635
+ // Add models from merged configuration
636
+ if (this.currentConfiguration?.models) {
637
+ Object.keys(this.currentConfiguration.models).forEach((model) => {
638
+ models.add(model);
639
+ });
640
+ }
641
+
642
+ return Array.from(models);
643
+ }
644
+
581
645
  /**
582
646
  * Resolve all configuration file paths
583
647
  */
@@ -874,6 +938,7 @@ export function loadWaveConfigFromFile(
874
938
  config.autoMemoryEnabled !== undefined
875
939
  ? config.autoMemoryEnabled
876
940
  : undefined,
941
+ models: config.models || undefined,
877
942
  };
878
943
  } catch (error) {
879
944
  if (error instanceof SyntaxError) {
@@ -1001,6 +1066,17 @@ export function loadMergedWaveConfig(
1001
1066
  if (config.autoMemoryEnabled !== undefined) {
1002
1067
  mergedConfig.autoMemoryEnabled = config.autoMemoryEnabled;
1003
1068
  }
1069
+
1070
+ // Merge models
1071
+ if (config.models) {
1072
+ if (!mergedConfig.models) mergedConfig.models = {};
1073
+ for (const [modelName, modelConfig] of Object.entries(config.models)) {
1074
+ if (!mergedConfig.models[modelName]) {
1075
+ mergedConfig.models[modelName] = {};
1076
+ }
1077
+ Object.assign(mergedConfig.models[modelName], modelConfig);
1078
+ }
1079
+ }
1004
1080
  }
1005
1081
 
1006
1082
  return {
@@ -1024,5 +1100,9 @@ export function loadMergedWaveConfig(
1024
1100
  : undefined,
1025
1101
  language: mergedConfig.language,
1026
1102
  autoMemoryEnabled: mergedConfig.autoMemoryEnabled,
1103
+ models:
1104
+ mergedConfig.models && Object.keys(mergedConfig.models).length > 0
1105
+ ? mergedConfig.models
1106
+ : undefined,
1027
1107
  };
1028
1108
  }
@@ -38,10 +38,20 @@ export const skillTool: ToolPlugin = {
38
38
  },
39
39
  },
40
40
 
41
- prompt: (args?: { availableSkills?: SkillMetadata[] }) => {
42
- const availableSkills = args?.availableSkills?.filter(
41
+ prompt: (args?: {
42
+ availableSkills?: SkillMetadata[];
43
+ isSubagent?: boolean;
44
+ }) => {
45
+ let availableSkills = args?.availableSkills?.filter(
43
46
  (skill) => !skill.disableModelInvocation,
44
47
  );
48
+
49
+ if (args?.isSubagent) {
50
+ availableSkills = availableSkills?.filter(
51
+ (skill) => skill.context !== "fork",
52
+ );
53
+ }
54
+
45
55
  if (!availableSkills || availableSkills.length === 0) {
46
56
  return `${SKILL_TOOL_DESCRIPTION} No skills are currently available.`;
47
57
  }
@@ -29,6 +29,7 @@ export interface ToolPlugin {
29
29
  availableSubagents?: SubagentConfiguration[];
30
30
  availableSkills?: SkillMetadata[];
31
31
  workdir?: string;
32
+ isSubagent?: boolean;
32
33
  }) => string;
33
34
  }
34
35
 
@@ -77,6 +77,7 @@ export interface AgentOptions {
77
77
  * These rules follow the standard permission rule syntax: `ToolName` or `ToolName(pattern)`.
78
78
  */
79
79
  disallowedTools?: string[];
80
+ [key: string]: unknown;
80
81
  }
81
82
 
82
83
  export interface AgentCallbacks
@@ -92,4 +93,6 @@ export interface AgentCallbacks
92
93
  tokens: number,
93
94
  ) => void;
94
95
  onBackgroundCurrentTask?: () => void;
96
+ onModelChange?: (model: string) => void;
97
+ onConfiguredModelsChange?: (models: string[]) => void;
95
98
  }
@@ -19,4 +19,5 @@ export interface ModelConfig {
19
19
  fastModel: string;
20
20
  maxTokens?: number;
21
21
  permissionMode?: PermissionMode;
22
+ [key: string]: unknown;
22
23
  }
@@ -8,6 +8,7 @@
8
8
 
9
9
  import type { HookEvent, HookEventConfig } from "./hooks.js";
10
10
  import type { PermissionMode } from "./permissions.js";
11
+ import type { ModelConfig } from "./config.js";
11
12
 
12
13
  export type Scope = "user" | "project" | "local";
13
14
 
@@ -34,6 +35,8 @@ export interface WaveConfiguration {
34
35
  language?: string;
35
36
  /** Whether auto-memory is enabled */
36
37
  autoMemoryEnabled?: boolean;
38
+ /** Model-specific configuration overrides */
39
+ models?: Record<string, Partial<ModelConfig>>;
37
40
  }
38
41
 
39
42
  /**
@@ -25,6 +25,7 @@ export type MessageBlock =
25
25
  | ToolBlock
26
26
  | ImageBlock
27
27
  | BangBlock
28
+ | SlashBlock
28
29
  | CompressBlock
29
30
  | ReasoningBlock
30
31
  | FileHistoryBlock;
@@ -32,10 +33,20 @@ export type MessageBlock =
32
33
  export interface TextBlock {
33
34
  type: "text";
34
35
  content: string;
35
- customCommandContent?: string;
36
36
  source?: MessageSource;
37
37
  }
38
38
 
39
+ export interface SlashBlock {
40
+ type: "slash";
41
+ command: string;
42
+ args?: string;
43
+ content?: string; // The expanded prompt (template + args + bash output)
44
+ result?: string; // The final output (e.g., from a forked skill)
45
+ stage: "running" | "success" | "error" | "aborted";
46
+ error?: string;
47
+ shortResult?: string; // Progress summary (e.g., "3 tools | 1,234 tokens")
48
+ }
49
+
39
50
  export interface ErrorBlock {
40
51
  type: "error";
41
52
  content: string;
@@ -5,6 +5,7 @@
5
5
 
6
6
  import {
7
7
  GatewayConfig,
8
+ ModelConfig,
8
9
  ConfigurationError,
9
10
  CONFIG_ERRORS,
10
11
  } from "../types/index.js";
@@ -89,11 +90,11 @@ export class ConfigValidator {
89
90
 
90
91
  /**
91
92
  * Validates model configuration (basic validation)
92
- * @param model - Agent model string
93
- * @param fastModel - Fast model string
93
+ * @param config - Model configuration object
94
94
  * @throws ConfigurationError if invalid
95
95
  */
96
- static validateModelConfig(model: string, fastModel: string): void {
96
+ static validateModelConfig(config: ModelConfig): void {
97
+ const { model, fastModel } = config;
97
98
  if (!model || typeof model !== "string" || model.trim() === "") {
98
99
  throw new ConfigurationError(
99
100
  "Agent model must be a non-empty string.",
@@ -123,5 +124,6 @@ export class ConfigValidator {
123
124
  export const configValidator = {
124
125
  validateGatewayConfig: ConfigValidator.validateGatewayConfig,
125
126
  validateMaxInputTokens: ConfigValidator.validateMaxInputTokens,
126
- validateModelConfig: ConfigValidator.validateModelConfig,
127
+ validateModelConfig: (config: ModelConfig) =>
128
+ ConfigValidator.validateModelConfig(config),
127
129
  };
@@ -246,7 +246,13 @@ export function setupAgentContainer(
246
246
  });
247
247
  container.register("CanUseToolCallback", canUseToolWithPermissionRequest);
248
248
 
249
- const liveConfigManager = new LiveConfigManager(container, { workdir });
249
+ const liveConfigManager = new LiveConfigManager(container, {
250
+ workdir,
251
+ onReload: () => {
252
+ const models = configurationService.getConfiguredModels();
253
+ callbacks.onConfiguredModelsChange?.(models);
254
+ },
255
+ });
250
256
  container.register("LiveConfigManager", liveConfigManager);
251
257
 
252
258
  const subagentManager = new SubagentManager(container, {
@@ -193,10 +193,26 @@ export function convertMessagesForAPI(
193
193
  ) {
194
194
  contentParts.push({
195
195
  type: "text",
196
- text: block.customCommandContent || block.content,
196
+ text: block.content,
197
197
  });
198
198
  }
199
199
 
200
+ // Handle SlashBlock
201
+ if (block.type === "slash") {
202
+ if (block.content && block.content.trim().length > 0) {
203
+ contentParts.push({
204
+ type: "text",
205
+ text: block.content,
206
+ });
207
+ }
208
+ if (block.result && block.result.trim().length > 0) {
209
+ contentParts.push({
210
+ type: "text",
211
+ text: `<local-command-stdout>\n${stripAnsiColors(block.result)}\n</local-command-stdout>`,
212
+ });
213
+ }
214
+ }
215
+
200
216
  // If there is an image, add image content
201
217
  if (
202
218
  block.type === "image" &&