winter-super-cli 2026.5.27 → 2026.5.29

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.
@@ -10,6 +10,7 @@ import { DesignCommands } from '../design/commands.js';
10
10
  import { SkillManager } from '../skills/manager.js';
11
11
  import { PluginManager } from '../plugins/manager.js';
12
12
  import { MCPClient } from '../mcp/client.js';
13
+ import { BenchmarkRunner } from '../ai/benchmark.js';
13
14
  import { redactSecrets } from './secret-env.js';
14
15
 
15
16
  export { redactSecrets } from './secret-env.js';
@@ -45,6 +46,7 @@ export class CommandParser {
45
46
  this.commands = {
46
47
  chat: this.handleChat.bind(this),
47
48
  call: this.handleCallAll.bind(this),
49
+ benchmark: this.handleBenchmark.bind(this),
48
50
  session: this.handleSession.bind(this),
49
51
  project: this.handleProject.bind(this),
50
52
  skill: this.handleSkill.bind(this),
@@ -158,6 +160,66 @@ export class CommandParser {
158
160
  }
159
161
  }
160
162
 
163
+ async handleBenchmark(args) {
164
+ const runner = new BenchmarkRunner(this.ai);
165
+
166
+ // Parse arguments: winter benchmark [providers...] [--tasks] [--questions]
167
+ const providerNames = [];
168
+ let runTasks = true;
169
+ let runQuestions = true;
170
+
171
+ for (const arg of args) {
172
+ if (arg === '--tasks-only') {
173
+ runQuestions = false;
174
+ } else if (arg === '--questions-only') {
175
+ runTasks = false;
176
+ } else if (arg.startsWith('--')) {
177
+ // skip unknown flags
178
+ } else {
179
+ providerNames.push(arg);
180
+ }
181
+ }
182
+
183
+ // If no providers specified, use active
184
+ if (providerNames.length === 0) {
185
+ await this.ai.init();
186
+ const active = this.ai.getActiveProvider();
187
+ if (active) providerNames.push(active);
188
+ // Also add any other ready providers
189
+ const providers = this.ai.listProviders?.() || [];
190
+ for (const p of providers) {
191
+ if (p.name !== active && p.ready && !providerNames.includes(p.name)) {
192
+ providerNames.push(p.name);
193
+ }
194
+ }
195
+ }
196
+
197
+ if (providerNames.length === 0) {
198
+ console.log(`${colors.yellow}No providers configured. Run 'winter config' first.${colors.reset}`);
199
+ return;
200
+ }
201
+
202
+ console.log(`\n${colors.dim}Benchmarking providers: ${providerNames.join(', ')}${colors.reset}`);
203
+ if (!runQuestions) console.log(`${colors.dim}(Coding tasks only)${colors.reset}`);
204
+ if (!runTasks) console.log(`${colors.dim}(Questions only)${colors.reset}`);
205
+ console.log('');
206
+
207
+ try {
208
+ const result = await runner.run(providerNames, { questions: runQuestions, tasks: runTasks });
209
+ console.log(runner.formatResults(result));
210
+
211
+ // Save to session history
212
+ try {
213
+ await this.session.addToHistory({
214
+ role: 'system',
215
+ content: `[Benchmark Results]\n${runner.formatHistorySummary(result)}`,
216
+ });
217
+ } catch {}
218
+ } catch (err) {
219
+ console.log(`${colors.red}${statusIcons.error} Benchmark failed: ${err.message}${colors.reset}`);
220
+ }
221
+ }
222
+
161
223
  async handleSession(args) {
162
224
  const [action, ...rest] = args;
163
225
 
@@ -14,7 +14,7 @@ export class ContextLoader {
14
14
  }
15
15
 
16
16
  getProjectInstructionFiles() {
17
- return ['winter.md', 'WINTER.md', 'CLAUDE.md', '.claude/CLAUDE.md'];
17
+ return ['winter.md', 'WINTER.md', 'CLAUDE.md', '.claude/CLAUDE.md', 'design.md', 'skill.md', 'rule.md'];
18
18
  }
19
19
 
20
20
  async readProjectInstructionFiles() {
@@ -68,6 +68,17 @@ export class ContextLoader {
68
68
  };
69
69
  }
70
70
 
71
+ async readTextIfExists(filePath, maxChars = 2000) {
72
+ try {
73
+ const stat = await fs.stat(filePath).catch(() => null);
74
+ if (!stat || !stat.isFile()) return '';
75
+ const content = await fs.readFile(filePath, 'utf8');
76
+ return this.compactText(content.replace(/^\uFEFF/, ''), maxChars, filePath);
77
+ } catch {
78
+ return '';
79
+ }
80
+ }
81
+
71
82
  getUserResourcePaths() {
72
83
  const home = homedir();
73
84
  return {
@@ -180,6 +191,58 @@ export class ContextLoader {
180
191
  }
181
192
  }
182
193
 
194
+ async getRequiredLocalResourceSummary() {
195
+ const paths = this.getResourcePaths();
196
+ const karpathyPath = path.join(paths.karpathy, 'CLAUDE.md');
197
+ const agentsPath = path.join(paths.agents, 'AGENTS.md');
198
+ const designReadmePath = path.join(paths.localRoot, 'awesome-design-md', 'README.md');
199
+
200
+ const [karpathy, agents, designReadme, designBrands] = await Promise.all([
201
+ this.readTextIfExists(karpathyPath, 2200),
202
+ this.readTextIfExists(agentsPath, 1800),
203
+ this.readTextIfExists(designReadmePath, 1600),
204
+ this.listPathEntries(paths.designs, 40),
205
+ ]);
206
+
207
+ const hasRequired = Boolean(karpathy || agents || designReadme || designBrands.length > 0);
208
+ if (!hasRequired) return '';
209
+
210
+ const lines = [];
211
+ lines.push('[Required Local Resource Rules]');
212
+ lines.push('- These rules are mandatory for every project session and every model size. Do not route quality down for smaller models.');
213
+ lines.push(`- Karpathy tools: ${path.relative(this.projectPath, karpathyPath)}`);
214
+ lines.push(' Apply: think before coding, state assumptions when needed, keep solutions simple, make surgical changes, and verify against concrete success criteria.');
215
+ lines.push(`- Agent rules: ${path.relative(this.projectPath, agentsPath)}`);
216
+ lines.push(' Apply: inspect source before edits, keep dependency and lockfile changes synced, prefer TypeScript for new TS/Next utilities, and use the appropriate dev/test command for the task.');
217
+ lines.push(`- Design system corpus: ${path.relative(this.projectPath, designReadmePath)} and ${path.relative(this.projectPath, paths.designs)}`);
218
+ lines.push(' Apply: for UI/brand/design tasks, search the design-md corpus first and follow the closest existing brand/design guidance instead of inventing style from scratch.');
219
+ lines.push('- Use Read/Grep/Glob to inspect the full resource files whenever task details require more than this startup summary.');
220
+
221
+ const brandNames = designBrands
222
+ .filter(item => item.isDirectory)
223
+ .map(item => item.name)
224
+ .slice(0, 16);
225
+ if (brandNames.length > 0) {
226
+ lines.push(`- Available design-md examples include: ${brandNames.join(', ')}${designBrands.length > brandNames.length ? ', ...' : ''}`);
227
+ }
228
+
229
+ const evidence = [];
230
+ if (/Think Before Coding|Simplicity First|Surgical Changes|Goal-Driven Execution/i.test(karpathy)) {
231
+ evidence.push('karpathy-tools confirms Think Before Coding, Simplicity First, Surgical Changes, and Goal-Driven Execution.');
232
+ }
233
+ if (/development server|dependencies|lockfile|TypeScript/i.test(agents)) {
234
+ evidence.push('agents.md confirms workflow, dependency, lockfile, and TypeScript guidance.');
235
+ }
236
+ if (/design|brand|guideline/i.test(designReadme)) {
237
+ evidence.push('awesome-design-md provides brand/design guidance to consult on UI work.');
238
+ }
239
+ if (evidence.length > 0) {
240
+ lines.push(`- Startup evidence: ${evidence.join(' ')}`);
241
+ }
242
+
243
+ return lines.join('\n');
244
+ }
245
+
183
246
  async getProjectSignals() {
184
247
  const signals = [];
185
248
 
@@ -90,12 +90,15 @@ export function normalizeToolCalls(toolCalls, parseArguments = parseToolArgument
90
90
  return toolCalls.map((tc, index) => {
91
91
  const fn = tc.function || {};
92
92
  const rawArgs = fn.arguments ?? tc.arguments ?? tc.input ?? {};
93
+ const parsedArgs = parseArguments(rawArgs);
94
+ const nestedName = parsedArgs?.name || parsedArgs?.tool || parsedArgs?.tool_name;
95
+ const nestedArgs = parsedArgs?.arguments ?? parsedArgs?.args ?? parsedArgs?.input;
93
96
 
94
97
  return {
95
98
  ...tc,
96
99
  id: tc.id || `call-${index}`,
97
- toolName: fn.name || tc.name || tc.tool_name || tc.type,
98
- toolArgs: parseArguments(rawArgs),
100
+ toolName: fn.name || tc.name || tc.tool_name || nestedName || tc.type,
101
+ toolArgs: nestedName && nestedArgs !== undefined ? parseArguments(nestedArgs) : parsedArgs,
99
102
  };
100
103
  });
101
104
  }
@@ -104,26 +107,68 @@ export function extractInlineToolCalls(content, idFactory = index => `inline-${D
104
107
  const text = String(content || '');
105
108
  const toolCalls = [];
106
109
  let cleaned = text;
107
- const callPattern = /<minimax:tool_call>\s*<invoke\s+name=["']([^"']+)["']>([\s\S]*?)<\/invoke>\s*<\/minimax:tool_call>/gi;
108
-
109
- cleaned = cleaned.replace(callPattern, (_match, name, body) => {
110
- const args = {};
111
- const paramPattern = /<parameter\s+name=["']([^"']+)["']>([\s\S]*?)<\/parameter>/gi;
112
- let param;
113
- while ((param = paramPattern.exec(body))) {
114
- args[param[1]] = decodeXmlEntities(param[2].trim());
115
- }
110
+ const pushToolCall = (name, args) => {
116
111
  toolCalls.push({
117
112
  id: idFactory(toolCalls.length),
118
113
  type: 'function',
119
114
  function: {
120
115
  name,
121
- arguments: JSON.stringify(args),
116
+ arguments: typeof args === 'string' ? args : JSON.stringify(args || {}),
122
117
  },
123
118
  });
119
+ };
120
+
121
+ const invokePattern = /(?:<minimax:tool_call>\s*)?<invoke\s+name=["']([^"']+)["']>([\s\S]*?)<\/invoke>\s*(?:<\/minimax:tool_call>)?/gi;
122
+
123
+ cleaned = cleaned.replace(invokePattern, (_match, name, body) => {
124
+ const args = {};
125
+ const paramPattern = /<parameter\s+name=["']([^"']+)["']>([\s\S]*?)<\/parameter>/gi;
126
+ let param;
127
+ while ((param = paramPattern.exec(body))) {
128
+ args[param[1]] = decodeXmlEntities(param[2].trim());
129
+ }
130
+ pushToolCall(name, args);
124
131
  return '';
125
132
  }).trim();
126
133
 
134
+ const namedToolPattern = /<tool_call\s+name=["']([^"']+)["']>([\s\S]*?)<\/tool_call>/gi;
135
+ cleaned = cleaned.replace(namedToolPattern, (_match, name, body) => {
136
+ pushToolCall(name, decodeXmlEntities(body.trim()));
137
+ return '';
138
+ }).trim();
139
+
140
+ const jsonToolPattern = /<tool_call>([\s\S]*?)<\/tool_call>/gi;
141
+ cleaned = cleaned.replace(jsonToolPattern, (_match, body) => {
142
+ const parsed = parseToolArguments(decodeXmlEntities(body.trim()));
143
+ const name = parsed.name || parsed.tool || parsed.tool_name;
144
+ const args = parsed.arguments ?? parsed.args ?? parsed.input ?? parsed;
145
+ if (name) pushToolCall(name, args);
146
+ return name ? '' : _match;
147
+ }).trim();
148
+
149
+ const functionPattern = /<function(?:\s+name=["']([^"']+)["']|=([^>\s]+))>([\s\S]*?)<\/function>/gi;
150
+ cleaned = cleaned.replace(functionPattern, (_match, quotedName, bareName, body) => {
151
+ pushToolCall(quotedName || bareName, decodeXmlEntities(body.trim()));
152
+ return '';
153
+ }).trim();
154
+
155
+ const fencedToolPattern = /```(?:tool|tool_call|function)\s*\n([\s\S]*?)```/gi;
156
+ cleaned = cleaned.replace(fencedToolPattern, (_match, body) => {
157
+ const trimmed = body.trim();
158
+ const parsed = parseToolArguments(trimmed);
159
+ const name = parsed.name || parsed.tool || parsed.tool_name;
160
+ if (name) {
161
+ pushToolCall(name, parsed.arguments ?? parsed.args ?? parsed.input ?? parsed);
162
+ return '';
163
+ }
164
+ const lineMatch = trimmed.match(/^([A-Za-z][\w.-]*)\s+([\s\S]+)$/);
165
+ if (lineMatch) {
166
+ pushToolCall(lineMatch[1], lineMatch[2].trim());
167
+ return '';
168
+ }
169
+ return _match;
170
+ }).trim();
171
+
127
172
  return { content: cleaned, toolCalls };
128
173
  }
129
174
 
@@ -154,6 +199,12 @@ export function parseToolArguments(rawArgs) {
154
199
  } catch {}
155
200
  }
156
201
 
202
+ for (const repaired of buildJsonRepairCandidates(extracted || text)) {
203
+ try {
204
+ return JSON.parse(repaired);
205
+ } catch {}
206
+ }
207
+
157
208
  return {
158
209
  __toolArgParseError: error.message,
159
210
  __rawToolArgs: text.length > 800 ? `${text.slice(0, 800)}...` : text,
@@ -161,6 +212,33 @@ export function parseToolArguments(rawArgs) {
161
212
  }
162
213
  }
163
214
 
215
+ export function buildJsonRepairCandidates(text) {
216
+ const value = String(text || '').trim();
217
+ if (!value) return [];
218
+ const candidates = [];
219
+
220
+ candidates.push(
221
+ value
222
+ .replace(/([{,]\s*)([A-Za-z_$][\w$-]*)(\s*:)/g, '$1"$2"$3')
223
+ .replace(/:\s*'([^'\\]*(?:\\.[^'\\]*)*)'/g, (_m, inner) => `: "${inner.replace(/"/g, '\\"')}"`)
224
+ .replace(/:\s*([^'",{}\[\]\r\n][^,}\]\r\n]*)/g, (_m, inner) => {
225
+ const trimmed = inner.trim();
226
+ if (trimmed.startsWith('"')) return `: ${trimmed}`;
227
+ if (/^(true|false|null|-?\d+(?:\.\d+)?)$/i.test(trimmed)) return `: ${trimmed}`;
228
+ return `: "${trimmed.replace(/"/g, '\\"')}"`;
229
+ })
230
+ .replace(/,\s*([}\]])/g, '$1')
231
+ );
232
+
233
+ candidates.push(
234
+ value
235
+ .replace(/'/g, '"')
236
+ .replace(/,\s*([}\]])/g, '$1')
237
+ );
238
+
239
+ return [...new Set(candidates)].filter(candidate => candidate && candidate !== value);
240
+ }
241
+
164
242
  export function extractFirstJsonObject(text) {
165
243
  const start = text.indexOf('{');
166
244
  if (start === -1) return null;
@@ -3,8 +3,9 @@
3
3
  * Extracted from WinterREPL to reduce repl.js size.
4
4
  */
5
5
  export class PromptBuilder {
6
- constructor({ session, tools, projectPath, sessionPermissionGrants, compactText, summarizePrompts } = {}) {
6
+ constructor({ session, ai, tools, projectPath, sessionPermissionGrants, compactText, summarizePrompts } = {}) {
7
7
  this.session = session;
8
+ this.ai = ai;
8
9
  this.tools = tools;
9
10
  this.projectPath = projectPath;
10
11
  this.sessionPermissionGrants = sessionPermissionGrants;
@@ -13,10 +14,12 @@ export class PromptBuilder {
13
14
  }
14
15
 
15
16
  buildSessionSignalsPrompt() {
16
- const activeProvider = this.session?.ai?.getActiveProvider?.() || 'unavailable';
17
- const activeModel = this.session?.ai?.providers?.[activeProvider]?.model || 'unavailable';
17
+ const activeProvider = this.ai?.getActiveProvider?.() || 'unavailable';
18
+ const activeModel = this.ai?.providers?.[activeProvider]?.model || 'unavailable';
18
19
  const sessionContext = this.session?.getContext?.() || {};
19
- const activeSkills = sessionContext.activeSkills?.value || [];
20
+ const activeSkills = Array.isArray(sessionContext.activeSkills?.value)
21
+ ? sessionContext.activeSkills.value
22
+ : (Array.isArray(sessionContext.activeSkills) ? sessionContext.activeSkills : []);
20
23
  const toolAllowlist = [...(this.sessionPermissionGrants || [])];
21
24
 
22
25
  return [
@@ -29,15 +32,25 @@ export class PromptBuilder {
29
32
  ].join('\n');
30
33
  }
31
34
 
32
- buildSystemPrompt(context = '') {
35
+ getRequiredLocalResources() {
36
+ const sessionContext = this.session?.getContext?.() || {};
37
+ const value = sessionContext.requiredLocalResources?.value ?? sessionContext.requiredLocalResources;
38
+ return typeof value === 'string' ? value : '';
39
+ }
40
+
41
+ buildSystemPrompt(context = '', options = {}) {
33
42
  const memories = this.session?.getMemory?.() || [];
34
43
  const plans = this.session?.getPlans?.() || [];
35
44
  const sessionContext = this.session?.getContext?.() || {};
36
45
  const environmentSummary = this.tools?.getRuntimeEnvironmentSummary?.() || this._defaultEnvironmentSummary();
46
+ const requiredLocalResources = this.getRequiredLocalResources();
37
47
 
38
48
  const memoryStr = memories.length > 0
39
49
  ? this._formatMemories(memories)
40
50
  : '';
51
+ const requiredResourcesStr = requiredLocalResources
52
+ ? `\n## Required Local Resource Rules\n${this._compactText(requiredLocalResources, 1800, 'required local resources')}`
53
+ : '';
41
54
  const plansStr = plans.length > 0
42
55
  ? this._formatPlans(plans)
43
56
  : '';
@@ -56,20 +69,22 @@ export class PromptBuilder {
56
69
  environmentSummary,
57
70
  ``,
58
71
  `## Core Principles`,
72
+ `0. Required Local Resources - Always follow Required Local Resource Rules when present; they override generic behavior.`,
59
73
  `1. Think Before Coding — State assumptions, ask when uncertain, surface tradeoffs.`,
60
74
  `2. Simplicity First — Minimum code that solves the problem. Nothing speculative.`,
61
75
  `3. Surgical Changes — Touch only what you must. Clean up only your own mess.`,
62
76
  `4. Goal-Driven Execution — Define success criteria. Loop until verified.`,
63
77
  ``,
64
78
  `## Tool Usage`,
65
- `You have access to various tools (Read, Write, Edit, Bash, Glob, Grep, etc.).`,
66
- `Use tools proactively. If you need information, look it up — don't guess.`,
79
+ `Use tools when they materially help. For coding tasks: inspect first, edit second, verify third.`,
80
+ `Prefer Read/Grep/Glob before editing. Use Write/Edit for file changes.`,
81
+ `When a task touches coding, agents, UI, brand, or design, inspect the relevant required local resource in depth before deciding.`,
67
82
  ``,
68
83
  `## Session`,
69
84
  `Working directory: ${this.projectPath}`,
70
85
  `Current session: ${this.session?.getSessionId?.()?.substring(0, 8) || 'unknown'}`,
71
- `${memoryStr}${plansStr}${skillsStr}${startupPlanStr}${sessionSignalsStr}`,
72
- context ? `\n## Project Context\n${this._compactText(context, 6000, 'project context')}` : '',
86
+ `${requiredResourcesStr}${memoryStr}${plansStr}${skillsStr}${startupPlanStr}${sessionSignalsStr}`,
87
+ context ? `\n## Project Context\n${this._compactText(context, options.projectContextBudget || 3200, 'project context')}` : '',
73
88
  ``,
74
89
  `Be helpful, be precise, and get things done. Always respond in Vietnamese.`,
75
90
  ].join('\n');
@@ -77,6 +92,10 @@ export class PromptBuilder {
77
92
 
78
93
  buildFastSystemPrompt() {
79
94
  const memories = this.session?.getMemory?.() || [];
95
+ const requiredLocalResources = this.getRequiredLocalResources();
96
+ const requiredResourcesStr = requiredLocalResources
97
+ ? `\nQuy tac resource bat buoc:\n${this._compactText(requiredLocalResources, 900, 'required local resources')}`
98
+ : '';
80
99
  const memoryStr = memories.length > 0
81
100
  ? `\nContext nhớ ngắn:\n${this._summarizePrompts(memories.slice(-8), {
82
101
  limit: 8,
@@ -90,6 +109,8 @@ export class PromptBuilder {
90
109
  'Bạn là Winter, trợ lý AI trả lời ngắn gọn bằng tiếng Việt.',
91
110
  'Ưu tiên dùng tool và context khi cần; không bịa thông tin.',
92
111
  'Nếu người dùng yêu cầu sửa file/chạy lệnh/đọc dự án thì hãy gọi tool tương ứng thay vì chỉ nói chung chung.',
112
+ 'Luon tuan thu Required Local Resource Rules neu co; khong ha chat luong theo model.',
113
+ requiredResourcesStr,
93
114
  memoryStr,
94
115
  ].filter(Boolean).join('\n');
95
116
  }
@@ -98,9 +119,13 @@ export class PromptBuilder {
98
119
  const memories = this.session?.getMemory?.() || [];
99
120
  const plans = this.session?.getPlans?.() || [];
100
121
  const sessionContext = this.session?.getContext?.() || {};
122
+ const requiredLocalResources = this.getRequiredLocalResources();
101
123
 
102
- const memoryStr = memories.length > 0 ? this._formatMemories(memories) : '';
103
- const plansStr = plans.length > 0 ? this._formatPlans(plans) : '';
124
+ const memoryStr = memories.length > 0 ? this._formatMemories(memories, { maxTotalChars: 900 }) : '';
125
+ const requiredResourcesStr = requiredLocalResources
126
+ ? `\n## Required Local Resource Rules\n${this._compactText(requiredLocalResources, 1600, 'required local resources')}`
127
+ : '';
128
+ const plansStr = plans.length > 0 ? this._formatPlans(plans, { maxTotalChars: 900 }) : '';
104
129
  const skillsStr = Array.isArray(sessionContext.activeSkills) && sessionContext.activeSkills.length > 0
105
130
  ? `\n## Auto-applied Skills\n${sessionContext.activeSkills.slice(0, 12).map(skill => `- ${skill}`).join('\n')}${sessionContext.activeSkills.length > 12 ? '\n- ...' : ''}`
106
131
  : '';
@@ -124,6 +149,7 @@ export class PromptBuilder {
124
149
 
125
150
  return [
126
151
  '## CRITICAL AI RULES (MUST FOLLOW STRICTLY):',
152
+ '0. [REQUIRED LOCAL RESOURCES]: Always obey Required Local Resource Rules when present. They override generic behavior and apply to every model size.',
127
153
  '1. [THINKING BEFORE CODING]: State assumptions, constraints, and a brief plan before making changes. Be thorough enough to be useful, and do not invent facts.',
128
154
  '2. [DESIGN EXCELLENCE]: Use rich aesthetics. Default to modern UI frameworks if applicable. Never output plain, ugly HTML/CSS. Ensure responsive, premium feel with micro-animations.',
129
155
  '3. [CODE QUALITY]: Write clean, modular, SOLID code. Check for syntax errors carefully. Do not generate incomplete code blocks.',
@@ -143,27 +169,27 @@ export class PromptBuilder {
143
169
  '## Project',
144
170
  `Working directory: ${this.projectPath}`,
145
171
  `Current session: ${this.session?.getSessionId?.()?.substring(0, 8) || 'unknown'}`,
146
- `${memoryStr}${plansStr}${skillsStr}${startupPlanStr}`,
147
- context ? `\n## Project Context\n${this._compactText(context, 6000, 'project context')}` : '',
172
+ `${requiredResourcesStr}${memoryStr}${plansStr}${skillsStr}${startupPlanStr}`,
173
+ context ? `\n## Project Context\n${this._compactText(context, 3200, 'project context')}` : '',
148
174
  ].join('\n');
149
175
  }
150
176
 
151
177
  /** @private */
152
- _formatMemories(memories) {
178
+ _formatMemories(memories, options = {}) {
153
179
  return `\n## Memories (Important Context)\n${this._summarizePrompts(memories, {
154
180
  limit: 8,
155
181
  maxEntryChars: 220,
156
- maxTotalChars: 1600,
182
+ maxTotalChars: options.maxTotalChars || 1200,
157
183
  mapper: memory => memory.text,
158
184
  })}`;
159
185
  }
160
186
 
161
187
  /** @private */
162
- _formatPlans(plans) {
188
+ _formatPlans(plans, options = {}) {
163
189
  return `\n## Active Plans & Tasks\n${this._summarizePrompts(plans, {
164
190
  limit: 6,
165
191
  maxEntryChars: 260,
166
- maxTotalChars: 1600,
192
+ maxTotalChars: options.maxTotalChars || 1200,
167
193
  mapper: plan => `[${plan.status}] ${plan.title}: ${plan.description}`,
168
194
  })}`;
169
195
  }
@@ -1,5 +1,6 @@
1
1
  import path from 'path';
2
2
  import { colors } from './snowflake-logo.js';
3
+ import { SLASH_COMMANDS } from './slash-commands.js';
3
4
 
4
5
  /**
5
6
  * Handle slash commands in the Winter REPL.
@@ -293,10 +294,21 @@ export async function handleSlashCommand(repl, input) {
293
294
  break;
294
295
  case '/model':
295
296
  if (args[0]) {
296
- await repl.tools.execute('SetModel', { model: args[0] });
297
- console.log(`${colors.green}✓ Model set to ${args[0]}${colors.reset}`);
297
+ const providerName = repl.ai?.getActiveProvider?.();
298
+ const model = args.join(' ');
299
+ if (!providerName) {
300
+ console.log(`${colors.red}No active provider${colors.reset}`);
301
+ break;
302
+ }
303
+ await repl.config?.setProviderModel?.(providerName, model);
304
+ if (repl.ai?.providers?.[providerName]) {
305
+ repl.ai.providers[providerName].model = model;
306
+ }
307
+ repl.ai?.updateActiveModelTier?.();
308
+ console.log(`${colors.green}OK Model for ${providerName}: ${model}${colors.reset}`);
298
309
  } else {
299
- console.log(`${colors.yellow}Usage: /model <id>${colors.reset}`);
310
+ const providerName = repl.ai?.getActiveProvider?.();
311
+ console.log(`${colors.cyan}Model: ${repl.ai?.providers?.[providerName]?.model || 'unknown'}${colors.reset}`);
300
312
  }
301
313
  break;
302
314
  case '/models':