erosolar-cli 1.7.14 → 1.7.15

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 (61) hide show
  1. package/dist/core/responseVerifier.d.ts +79 -0
  2. package/dist/core/responseVerifier.d.ts.map +1 -0
  3. package/dist/core/responseVerifier.js +443 -0
  4. package/dist/core/responseVerifier.js.map +1 -0
  5. package/dist/shell/interactiveShell.d.ts +5 -0
  6. package/dist/shell/interactiveShell.d.ts.map +1 -1
  7. package/dist/shell/interactiveShell.js +38 -0
  8. package/dist/shell/interactiveShell.js.map +1 -1
  9. package/dist/ui/ShellUIAdapter.d.ts +3 -0
  10. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  11. package/dist/ui/ShellUIAdapter.js +4 -10
  12. package/dist/ui/ShellUIAdapter.js.map +1 -1
  13. package/dist/ui/persistentPrompt.d.ts +4 -0
  14. package/dist/ui/persistentPrompt.d.ts.map +1 -1
  15. package/dist/ui/persistentPrompt.js +10 -11
  16. package/dist/ui/persistentPrompt.js.map +1 -1
  17. package/package.json +1 -1
  18. package/dist/bin/core/agent.js +0 -362
  19. package/dist/bin/core/agentProfileManifest.js +0 -187
  20. package/dist/bin/core/agentProfiles.js +0 -34
  21. package/dist/bin/core/agentRulebook.js +0 -135
  22. package/dist/bin/core/agentSchemaLoader.js +0 -233
  23. package/dist/bin/core/contextManager.js +0 -412
  24. package/dist/bin/core/contextWindow.js +0 -122
  25. package/dist/bin/core/customCommands.js +0 -80
  26. package/dist/bin/core/errors/apiKeyErrors.js +0 -114
  27. package/dist/bin/core/errors/errorTypes.js +0 -340
  28. package/dist/bin/core/errors/safetyValidator.js +0 -304
  29. package/dist/bin/core/errors.js +0 -32
  30. package/dist/bin/core/modelDiscovery.js +0 -755
  31. package/dist/bin/core/preferences.js +0 -224
  32. package/dist/bin/core/schemaValidator.js +0 -92
  33. package/dist/bin/core/secretStore.js +0 -199
  34. package/dist/bin/core/sessionStore.js +0 -187
  35. package/dist/bin/core/toolRuntime.js +0 -290
  36. package/dist/bin/core/types.js +0 -1
  37. package/dist/bin/shell/bracketedPasteManager.js +0 -350
  38. package/dist/bin/shell/fileChangeTracker.js +0 -65
  39. package/dist/bin/shell/interactiveShell.js +0 -2908
  40. package/dist/bin/shell/liveStatus.js +0 -78
  41. package/dist/bin/shell/shellApp.js +0 -290
  42. package/dist/bin/shell/systemPrompt.js +0 -60
  43. package/dist/bin/shell/updateManager.js +0 -108
  44. package/dist/bin/ui/ShellUIAdapter.js +0 -459
  45. package/dist/bin/ui/UnifiedUIController.js +0 -183
  46. package/dist/bin/ui/animation/AnimationScheduler.js +0 -430
  47. package/dist/bin/ui/codeHighlighter.js +0 -854
  48. package/dist/bin/ui/designSystem.js +0 -121
  49. package/dist/bin/ui/display.js +0 -1222
  50. package/dist/bin/ui/interrupts/InterruptManager.js +0 -437
  51. package/dist/bin/ui/layout.js +0 -139
  52. package/dist/bin/ui/orchestration/StatusOrchestrator.js +0 -403
  53. package/dist/bin/ui/outputMode.js +0 -38
  54. package/dist/bin/ui/persistentPrompt.js +0 -183
  55. package/dist/bin/ui/richText.js +0 -338
  56. package/dist/bin/ui/shortcutsHelp.js +0 -87
  57. package/dist/bin/ui/telemetry/UITelemetry.js +0 -443
  58. package/dist/bin/ui/textHighlighter.js +0 -210
  59. package/dist/bin/ui/theme.js +0 -116
  60. package/dist/bin/ui/toolDisplay.js +0 -423
  61. package/dist/bin/ui/toolDisplayAdapter.js +0 -357
@@ -1,362 +0,0 @@
1
- /**
2
- * Maximum number of context overflow recovery attempts
3
- */
4
- const MAX_CONTEXT_RECOVERY_ATTEMPTS = 3;
5
- /**
6
- * Check if an error is a context overflow error
7
- */
8
- function isContextOverflowError(error) {
9
- if (!(error instanceof Error))
10
- return false;
11
- const message = error.message.toLowerCase();
12
- return (message.includes('context length') ||
13
- message.includes('token') && (message.includes('limit') || message.includes('exceed') || message.includes('maximum')) ||
14
- message.includes('too long') ||
15
- message.includes('too many tokens') ||
16
- message.includes('max_tokens') ||
17
- message.includes('context window'));
18
- }
19
- export class AgentRuntime {
20
- constructor(options) {
21
- this.messages = [];
22
- this.activeRun = null;
23
- this.provider = options.provider;
24
- this.toolRuntime = options.toolRuntime;
25
- this.callbacks = options.callbacks ?? {};
26
- this.contextManager = options.contextManager ?? null;
27
- const trimmedPrompt = options.systemPrompt.trim();
28
- this.baseSystemPrompt = trimmedPrompt || null;
29
- if (trimmedPrompt) {
30
- this.messages.push({ role: 'system', content: trimmedPrompt });
31
- }
32
- }
33
- async send(text, useStreaming = false) {
34
- const prompt = text.trim();
35
- if (!prompt) {
36
- return '';
37
- }
38
- this.messages.push({ role: 'user', content: prompt });
39
- const run = { startedAt: Date.now() };
40
- this.activeRun = run;
41
- try {
42
- if (useStreaming && this.provider.generateStream) {
43
- return await this.processConversationStreaming();
44
- }
45
- return await this.processConversation();
46
- }
47
- finally {
48
- if (this.activeRun === run) {
49
- this.activeRun = null;
50
- }
51
- }
52
- }
53
- async processConversation() {
54
- let contextRecoveryAttempts = 0;
55
- while (true) {
56
- // Prune messages if approaching context limit (BEFORE generation)
57
- await this.pruneMessagesIfNeeded();
58
- try {
59
- const response = await this.provider.generate(this.messages, this.providerTools);
60
- const usage = response.usage ?? null;
61
- const contextStats = this.getContextStats();
62
- // Reset recovery attempts on successful generation
63
- contextRecoveryAttempts = 0;
64
- if (response.type === 'tool_calls') {
65
- const narration = response.content?.trim();
66
- if (narration) {
67
- this.emitAssistantMessage(narration, { isFinal: false, usage, contextStats });
68
- }
69
- const assistantMessage = {
70
- role: 'assistant',
71
- content: response.content ?? '',
72
- };
73
- if (response.toolCalls?.length) {
74
- assistantMessage.toolCalls = response.toolCalls;
75
- }
76
- this.messages.push(assistantMessage);
77
- await this.resolveToolCalls(response.toolCalls);
78
- continue;
79
- }
80
- const reply = response.content?.trim() ?? '';
81
- if (reply) {
82
- this.emitAssistantMessage(reply, { isFinal: true, usage, contextStats });
83
- }
84
- this.messages.push({ role: 'assistant', content: reply });
85
- return reply;
86
- }
87
- catch (error) {
88
- // Auto-recover from context overflow errors
89
- if (isContextOverflowError(error) && contextRecoveryAttempts < MAX_CONTEXT_RECOVERY_ATTEMPTS) {
90
- contextRecoveryAttempts++;
91
- const recovered = await this.recoverFromContextOverflow(contextRecoveryAttempts);
92
- if (recovered) {
93
- // Retry the generation with reduced context
94
- continue;
95
- }
96
- }
97
- // Re-throw if not recoverable or recovery failed
98
- throw error;
99
- }
100
- }
101
- }
102
- async processConversationStreaming() {
103
- if (!this.provider.generateStream) {
104
- return this.processConversation();
105
- }
106
- let contextRecoveryAttempts = 0;
107
- while (true) {
108
- // Prune messages if approaching context limit (BEFORE generation)
109
- await this.pruneMessagesIfNeeded();
110
- try {
111
- let fullContent = '';
112
- const toolCalls = [];
113
- let usage = null;
114
- const stream = this.provider.generateStream(this.messages, this.providerTools);
115
- for await (const chunk of stream) {
116
- if (chunk.type === 'content' && chunk.content) {
117
- fullContent += chunk.content;
118
- this.callbacks.onStreamChunk?.(chunk.content);
119
- }
120
- else if (chunk.type === 'tool_call' && chunk.toolCall) {
121
- toolCalls.push(chunk.toolCall);
122
- }
123
- else if (chunk.type === 'usage' && chunk.usage) {
124
- usage = chunk.usage;
125
- }
126
- }
127
- // Reset recovery attempts on successful generation
128
- contextRecoveryAttempts = 0;
129
- const contextStats = this.getContextStats();
130
- // Check if we got tool calls
131
- if (toolCalls.length > 0) {
132
- const narration = fullContent.trim();
133
- if (narration) {
134
- this.emitAssistantMessage(narration, { isFinal: false, usage, contextStats });
135
- }
136
- const assistantMessage = {
137
- role: 'assistant',
138
- content: fullContent,
139
- toolCalls,
140
- };
141
- this.messages.push(assistantMessage);
142
- await this.resolveToolCalls(toolCalls);
143
- continue;
144
- }
145
- // Final message
146
- const reply = fullContent.trim();
147
- if (reply) {
148
- this.emitAssistantMessage(reply, { isFinal: true, usage, contextStats });
149
- }
150
- this.messages.push({ role: 'assistant', content: reply });
151
- return reply;
152
- }
153
- catch (error) {
154
- // Auto-recover from context overflow errors
155
- if (isContextOverflowError(error) && contextRecoveryAttempts < MAX_CONTEXT_RECOVERY_ATTEMPTS) {
156
- contextRecoveryAttempts++;
157
- const recovered = await this.recoverFromContextOverflow(contextRecoveryAttempts);
158
- if (recovered) {
159
- // Retry the generation with reduced context
160
- continue;
161
- }
162
- }
163
- // Re-throw if not recoverable or recovery failed
164
- throw error;
165
- }
166
- }
167
- }
168
- async resolveToolCalls(toolCalls) {
169
- // Execute all tool calls in parallel for better performance
170
- const results = await Promise.all(toolCalls.map(async (call) => ({
171
- call,
172
- output: await this.toolRuntime.execute(call),
173
- })));
174
- // Add results to messages in the same order as tool calls
175
- for (const { call, output } of results) {
176
- this.messages.push({
177
- role: 'tool',
178
- name: call.name,
179
- toolCallId: call.id,
180
- content: output,
181
- });
182
- }
183
- }
184
- get providerTools() {
185
- return this.toolRuntime.listProviderTools();
186
- }
187
- emitAssistantMessage(content, metadata) {
188
- if (!content) {
189
- return;
190
- }
191
- const elapsedMs = this.activeRun ? Date.now() - this.activeRun.startedAt : undefined;
192
- const payload = { ...metadata };
193
- if (typeof elapsedMs === 'number') {
194
- payload.elapsedMs = elapsedMs;
195
- }
196
- this.callbacks.onAssistantMessage?.(content, payload);
197
- }
198
- getHistory() {
199
- return this.messages.map(cloneMessage);
200
- }
201
- loadHistory(history) {
202
- this.messages.length = 0;
203
- if (history.length === 0) {
204
- if (this.baseSystemPrompt) {
205
- this.messages.push({ role: 'system', content: this.baseSystemPrompt });
206
- }
207
- return;
208
- }
209
- for (const message of history) {
210
- this.messages.push(cloneMessage(message));
211
- }
212
- }
213
- clearHistory() {
214
- this.messages.length = 0;
215
- if (this.baseSystemPrompt) {
216
- this.messages.push({ role: 'system', content: this.baseSystemPrompt });
217
- }
218
- }
219
- /**
220
- * Prune messages if approaching context limit
221
- *
222
- * This runs BEFORE each generation to ensure we stay within budget.
223
- * If LLM summarization is available, it will create intelligent summaries
224
- * instead of just removing old messages.
225
- */
226
- async pruneMessagesIfNeeded() {
227
- if (!this.contextManager) {
228
- return;
229
- }
230
- if (this.contextManager.isApproachingLimit(this.messages)) {
231
- // Try LLM-based summarization first (preserves context better)
232
- const result = await this.contextManager.pruneMessagesWithSummary(this.messages);
233
- if (result.removed > 0) {
234
- // Replace messages with pruned/summarized version
235
- this.messages.length = 0;
236
- this.messages.push(...result.pruned);
237
- // Notify callback with enriched stats
238
- const stats = this.contextManager.getStats(this.messages);
239
- const enrichedStats = {
240
- ...stats,
241
- summarized: result.summarized,
242
- method: result.summarized ? 'llm-summary' : 'simple-prune',
243
- };
244
- this.callbacks.onContextPruned?.(result.removed, enrichedStats);
245
- if (process.env['DEBUG_CONTEXT']) {
246
- console.warn(`[Context Manager] ${result.summarized ? 'Summarized' : 'Pruned'} ${result.removed} messages. ` +
247
- `Tokens: ${stats.totalTokens} (${stats.percentage}%)`);
248
- }
249
- }
250
- }
251
- }
252
- /**
253
- * Get current context statistics
254
- */
255
- getContextStats() {
256
- if (!this.contextManager) {
257
- return null;
258
- }
259
- return this.contextManager.getStats(this.messages);
260
- }
261
- /**
262
- * Get context manager instance
263
- */
264
- getContextManager() {
265
- return this.contextManager;
266
- }
267
- /**
268
- * Auto-recover from context overflow errors by aggressively pruning messages.
269
- *
270
- * This is called when an API call fails due to context length exceeding limits.
271
- * It performs increasingly aggressive pruning on each attempt:
272
- * - Attempt 1: Remove 30% of oldest messages
273
- * - Attempt 2: Remove 50% of oldest messages
274
- * - Attempt 3: Remove 70% of oldest messages (keep only recent)
275
- *
276
- * @returns true if recovery was successful (context was reduced)
277
- */
278
- async recoverFromContextOverflow(attempt) {
279
- // Calculate reduction percentage based on attempt
280
- const reductionPercentages = [0.3, 0.5, 0.7];
281
- const reductionPercent = reductionPercentages[attempt - 1] ?? 0.7;
282
- // Notify UI about recovery attempt
283
- const message = `Context overflow detected. Auto-squishing context (attempt ${attempt}/${MAX_CONTEXT_RECOVERY_ATTEMPTS}, removing ${Math.round(reductionPercent * 100)}% of history)...`;
284
- this.callbacks.onContextRecovery?.(attempt, MAX_CONTEXT_RECOVERY_ATTEMPTS, message);
285
- this.callbacks.onContextSquishing?.(message);
286
- // Separate system messages from conversation
287
- const systemMessages = [];
288
- const conversationMessages = [];
289
- for (const msg of this.messages) {
290
- if (msg.role === 'system') {
291
- systemMessages.push(msg);
292
- }
293
- else {
294
- conversationMessages.push(msg);
295
- }
296
- }
297
- // Calculate how many messages to remove
298
- const removeCount = Math.floor(conversationMessages.length * reductionPercent);
299
- if (removeCount === 0 || conversationMessages.length <= 2) {
300
- // Nothing to remove or too few messages - can't recover
301
- return false;
302
- }
303
- // Keep recent messages
304
- const keepMessages = conversationMessages.slice(removeCount);
305
- // Rebuild message array
306
- this.messages.length = 0;
307
- // Add system messages
308
- for (const sys of systemMessages) {
309
- this.messages.push(sys);
310
- }
311
- // Add summary notice
312
- this.messages.push({
313
- role: 'system',
314
- content: `[Auto Context Recovery] Removed ${removeCount} earlier messages to stay within token limits. Conversation context has been compressed.`,
315
- });
316
- // Add remaining conversation
317
- for (const msg of keepMessages) {
318
- this.messages.push(msg);
319
- }
320
- // Notify about pruning
321
- const stats = this.contextManager?.getStats(this.messages) ?? {};
322
- this.callbacks.onContextPruned?.(removeCount, {
323
- ...stats,
324
- method: 'emergency-recovery',
325
- attempt,
326
- removedPercent: reductionPercent * 100,
327
- });
328
- return true;
329
- }
330
- }
331
- function cloneMessage(message) {
332
- switch (message.role) {
333
- case 'assistant':
334
- const clone = {
335
- role: 'assistant',
336
- content: message.content,
337
- };
338
- if (message.toolCalls) {
339
- clone.toolCalls = message.toolCalls.map(cloneToolCall);
340
- }
341
- return clone;
342
- case 'tool':
343
- return {
344
- role: 'tool',
345
- name: message.name,
346
- content: message.content,
347
- toolCallId: message.toolCallId,
348
- };
349
- case 'system':
350
- return { role: 'system', content: message.content };
351
- case 'user':
352
- default:
353
- return { role: 'user', content: message.content };
354
- }
355
- }
356
- function cloneToolCall(call) {
357
- return {
358
- id: call.id,
359
- name: call.name,
360
- arguments: { ...(call.arguments ?? {}) },
361
- };
362
- }
@@ -1,187 +0,0 @@
1
- import { getAgentSchemas } from './agentSchemaLoader.js';
2
- // Load from centralized schema
3
- const manifest = (() => {
4
- const schemas = getAgentSchemas();
5
- const rawManifest = {
6
- contractVersion: schemas.contractVersion,
7
- version: schemas.version,
8
- label: schemas.label,
9
- description: schemas.description,
10
- profiles: schemas.profiles,
11
- metadata: schemas.metadata,
12
- };
13
- return normalizeManifest(rawManifest);
14
- })();
15
- export function getAgentProfileManifest() {
16
- return manifest;
17
- }
18
- function normalizeManifest(raw) {
19
- if (!isRecord(raw)) {
20
- throw new Error('Agent profile manifest is malformed: expected an object.');
21
- }
22
- const profiles = Array.isArray(raw.profiles) ? raw.profiles.map(normalizeProfileEntry) : null;
23
- if (!profiles?.length) {
24
- throw new Error('Agent profile manifest must include at least one profile entry.');
25
- }
26
- const seen = new Set();
27
- for (const entry of profiles) {
28
- if (seen.has(entry.name)) {
29
- throw new Error(`Agent profile manifest contains duplicate profile id "${entry.name}".`);
30
- }
31
- seen.add(entry.name);
32
- }
33
- const manifest = {
34
- contractVersion: requireString(raw.contractVersion, 'contractVersion'),
35
- version: requireString(raw.version, 'version'),
36
- profiles,
37
- };
38
- const label = optionalString(raw.label);
39
- if (label) {
40
- manifest.label = label;
41
- }
42
- const description = optionalString(raw.description);
43
- if (description) {
44
- manifest.description = description;
45
- }
46
- const metadata = normalizeRecord(raw.metadata);
47
- if (metadata) {
48
- manifest.metadata = metadata;
49
- }
50
- return manifest;
51
- }
52
- function normalizeProfileEntry(raw) {
53
- const name = requireString(raw.name, 'profile.name');
54
- const profile = {
55
- name,
56
- label: requireString(raw.label, `profiles["${name}"].label`),
57
- defaultProvider: requireProvider(raw.defaultProvider, name),
58
- defaultModel: requireString(raw.defaultModel, `profiles["${name}"].defaultModel`),
59
- systemPrompt: normalizePrompt(raw.systemPrompt, name),
60
- rulebook: normalizeRulebook(raw.rulebook, name),
61
- };
62
- const description = optionalString(raw.description);
63
- if (description) {
64
- profile.description = description;
65
- }
66
- const temperature = optionalNumber(raw.temperature, `profiles["${name}"].temperature`);
67
- if (typeof temperature === 'number') {
68
- profile.temperature = temperature;
69
- }
70
- const maxTokens = optionalInteger(raw.maxTokens, `profiles["${name}"].maxTokens`);
71
- if (typeof maxTokens === 'number') {
72
- profile.maxTokens = maxTokens;
73
- }
74
- const metadata = normalizeRecord(raw.metadata);
75
- if (metadata) {
76
- profile.metadata = metadata;
77
- }
78
- return profile;
79
- }
80
- function normalizePrompt(raw, profile) {
81
- if (!isRecord(raw)) {
82
- throw new Error(`Profile "${profile}" is missing a valid systemPrompt definition.`);
83
- }
84
- if (raw.type === 'literal') {
85
- return normalizeLiteralPrompt(raw, profile);
86
- }
87
- if (raw.type === 'rulebook') {
88
- return normalizeRulebookPrompt(raw, profile);
89
- }
90
- throw new Error(`Profile "${profile}" has an unsupported systemPrompt type.`);
91
- }
92
- function normalizeLiteralPrompt(raw, profile) {
93
- const prompt = {
94
- type: 'literal',
95
- content: requireString(raw.content, `profiles["${profile}"].systemPrompt.content`),
96
- };
97
- const metadata = normalizeRecord(raw.metadata);
98
- if (metadata) {
99
- prompt.metadata = metadata;
100
- }
101
- return prompt;
102
- }
103
- function normalizeRulebookPrompt(raw, _profile) {
104
- const prompt = { type: 'rulebook' };
105
- const template = optionalString(raw.template);
106
- if (template) {
107
- prompt.template = template;
108
- }
109
- const metadata = normalizeRecord(raw.metadata);
110
- if (metadata) {
111
- prompt.metadata = metadata;
112
- }
113
- return prompt;
114
- }
115
- function normalizeRulebook(raw, profile) {
116
- if (!isRecord(raw)) {
117
- throw new Error(`Profile "${profile}" is missing a valid rulebook reference.`);
118
- }
119
- const reference = {
120
- file: requireString(raw.file, `profiles["${profile}"].rulebook.file`),
121
- };
122
- const version = optionalString(raw.version);
123
- if (version) {
124
- reference.version = version;
125
- }
126
- const contractVersion = optionalString(raw.contractVersion);
127
- if (contractVersion) {
128
- reference.contractVersion = contractVersion;
129
- }
130
- const description = optionalString(raw.description);
131
- if (description) {
132
- reference.description = description;
133
- }
134
- const metadata = normalizeRecord(raw.metadata);
135
- if (metadata) {
136
- reference.metadata = metadata;
137
- }
138
- return reference;
139
- }
140
- function requireProvider(value, profile) {
141
- const resolved = requireString(value, `profiles["${profile}"].defaultProvider`);
142
- return resolved;
143
- }
144
- function requireString(value, field) {
145
- if (typeof value !== 'string') {
146
- throw new Error(`Agent profile manifest is missing required field "${field}".`);
147
- }
148
- const trimmed = value.trim();
149
- if (!trimmed) {
150
- throw new Error(`Agent profile manifest field "${field}" cannot be blank.`);
151
- }
152
- return trimmed;
153
- }
154
- function optionalString(value) {
155
- if (typeof value !== 'string') {
156
- return undefined;
157
- }
158
- const trimmed = value.trim();
159
- return trimmed.length ? trimmed : undefined;
160
- }
161
- function optionalNumber(value, field) {
162
- if (value === undefined) {
163
- return undefined;
164
- }
165
- if (typeof value !== 'number' || !Number.isFinite(value)) {
166
- throw new Error(`Agent profile manifest field "${field}" must be a finite number when provided.`);
167
- }
168
- return value;
169
- }
170
- function optionalInteger(value, field) {
171
- if (value === undefined) {
172
- return undefined;
173
- }
174
- if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {
175
- throw new Error(`Agent profile manifest field "${field}" must be a positive integer when provided.`);
176
- }
177
- return value;
178
- }
179
- function normalizeRecord(record) {
180
- if (!isRecord(record)) {
181
- return undefined;
182
- }
183
- return { ...record };
184
- }
185
- function isRecord(value) {
186
- return typeof value === 'object' && value !== null && !Array.isArray(value);
187
- }
@@ -1,34 +0,0 @@
1
- const registry = new Map();
2
- export function registerAgentProfile(blueprint) {
3
- if (!blueprint?.name) {
4
- throw new Error('Agent profile name is required.');
5
- }
6
- const trimmedName = blueprint.name.trim();
7
- if (!trimmedName) {
8
- throw new Error('Agent profile name cannot be blank.');
9
- }
10
- const payload = Object.freeze({
11
- ...blueprint,
12
- name: trimmedName,
13
- label: blueprint.label.trim() || trimmedName,
14
- frozen: true,
15
- });
16
- registry.set(trimmedName, payload);
17
- }
18
- export function hasAgentProfile(name) {
19
- return registry.has(name.trim());
20
- }
21
- export function getAgentProfile(name) {
22
- const profile = registry.get(name.trim());
23
- if (!profile) {
24
- const known = listAgentProfiles()
25
- .map((entry) => entry.name)
26
- .sort()
27
- .join(', ');
28
- throw new Error(`Unknown profile "${name}". Registered profiles: ${known || 'none'}.`);
29
- }
30
- return profile;
31
- }
32
- export function listAgentProfiles() {
33
- return Array.from(registry.values());
34
- }