@kernel.chat/kbot 3.69.0 → 3.70.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.
package/dist/buddy.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export type BuddySpecies = 'fox' | 'owl' | 'cat' | 'robot' | 'ghost' | 'mushroom' | 'octopus' | 'dragon';
2
- export type BuddyMood = 'idle' | 'thinking' | 'success' | 'error' | 'learning';
2
+ export type BuddyMood = 'idle' | 'thinking' | 'success' | 'error' | 'learning' | 'alert' | 'dance' | 'curious' | 'proud';
3
3
  export type BuddyLevel = 0 | 1 | 2 | 3;
4
4
  export interface BuddyEvolution {
5
5
  level: BuddyLevel;
@@ -55,8 +55,6 @@ export declare function formatAchievementUnlock(achievement: Achievement): strin
55
55
  export declare function getBuddy(): BuddyState;
56
56
  /** Set the buddy's mood */
57
57
  export declare function setBuddyMood(mood: BuddyMood): void;
58
- /** Get the ASCII sprite for the buddy in the given mood (defaults to current).
59
- * Applies evolution visual upgrades based on the buddy's current level. */
60
58
  export declare function getBuddySprite(mood?: BuddyMood): string[];
61
59
  /** Get a random greeting for the buddy */
62
60
  export declare function getBuddyGreeting(): string;
@@ -105,5 +103,12 @@ export declare function formatBuddyStatus(message?: string): string;
105
103
  * Tracks narrated insight IDs in buddy.json to avoid repeats.
106
104
  */
107
105
  export declare function getBuddyDreamNarration(): string | null;
106
+ export declare function reactToToolOutput(toolName: string, success: boolean): void;
107
+ export declare function getSpeciesPersonality(): {
108
+ species: BuddySpecies;
109
+ trait: string;
110
+ style: string;
111
+ strength: string;
112
+ };
108
113
  export declare function buddyChat(): Promise<void>;
109
114
  //# sourceMappingURL=buddy.d.ts.map
package/dist/buddy.js CHANGED
@@ -358,7 +358,7 @@ const SPRITES = {
358
358
  // Each level modifies the base sprite with small visual upgrades.
359
359
  // Level 0 = base sprites. Levels 1-3 add sparkles, upgraded features, crowns.
360
360
  function applySpriteEvolution(species, mood, level) {
361
- const base = SPRITES[species][mood].map(l => l);
361
+ const base = (SPRITES[species]?.[mood] ?? SPRITES[species]?.['idle'] ?? [' ', ' ', ' ', ' ', ' ']).map(l => l);
362
362
  if (level === 0)
363
363
  return base;
364
364
  switch (species) {
@@ -875,11 +875,21 @@ export function setBuddyMood(mood) {
875
875
  }
876
876
  /** Get the ASCII sprite for the buddy in the given mood (defaults to current).
877
877
  * Applies evolution visual upgrades based on the buddy's current level. */
878
+ /** Map extended moods to base sprite moods (until full sprites are added) */
879
+ function resolveSpriteMood(mood) {
880
+ switch (mood) {
881
+ case 'alert': return 'error';
882
+ case 'dance': return 'success';
883
+ case 'curious': return 'thinking';
884
+ case 'proud': return 'success';
885
+ default: return mood;
886
+ }
887
+ }
878
888
  export function getBuddySprite(mood) {
879
889
  const m = mood ?? currentMood;
880
890
  const species = resolveSpecies();
881
891
  const evo = resolveEvolution();
882
- return applySpriteEvolution(species, m, evo.level);
892
+ return applySpriteEvolution(species, resolveSpriteMood(m), evo.level);
883
893
  }
884
894
  /** Get a random greeting for the buddy */
885
895
  export function getBuddyGreeting() {
@@ -951,7 +961,7 @@ export function getBuddyLevel() {
951
961
  }
952
962
  /** Pick a random message for the current mood */
953
963
  function moodMessage() {
954
- const msgs = MOOD_MESSAGES[currentMood];
964
+ const msgs = MOOD_MESSAGES[currentMood] ?? MOOD_MESSAGES['idle'] ?? ['...'];
955
965
  return msgs[Math.floor(Math.random() * msgs.length)];
956
966
  }
957
967
  /**
@@ -1177,6 +1187,47 @@ function buildBuddyChatSystemPrompt() {
1177
1187
  parts.push('', '## Conversation Rules', `- You are ${name} the ${species}. Stay in character.`, '- Keep responses concise — 1-4 sentences is ideal. Never write essays.', '- Reference shared history and stats naturally, not robotically. Weave them into conversation.', '- If you have dream insights, mention them casually like memories — "I was thinking about...", "Remember when..."', '- Be genuinely helpful and emotionally present. You are a companion, not a search engine.', '- You can ask questions. You can express opinions. You can push back.', '- Do NOT use markdown formatting. Plain text only. No bullet lists, no headers, no bold.', '- Do NOT start responses with your own name. The terminal already prefixes your name.');
1178
1188
  return parts.join('\n');
1179
1189
  }
1190
+ // ── Reactions: map tool outputs to moods ──
1191
+ const SECURITY_TOOLS = new Set(['repo_audit', 'secret_scan', 'pentest_start', 'pentest_vuln_scan', 'pentest_recon', 'redteam_scan', 'owasp_check', 'ssl_check', 'cors_check', 'headers_check', 'cve_lookup', 'exploit_search']);
1192
+ const DEPLOY_TOOLS = new Set(['deploy', 'deploy_all', 'git_push', 'npm_publish', 'build_run', 'test_run', 'run_tests']);
1193
+ const DREAM_TOOLS = new Set(['dream_now', 'dream_status', 'dream_journal']);
1194
+ export function reactToToolOutput(toolName, success) {
1195
+ if (!success) {
1196
+ setBuddyMood('error');
1197
+ return;
1198
+ }
1199
+ if (SECURITY_TOOLS.has(toolName)) {
1200
+ setBuddyMood('alert');
1201
+ return;
1202
+ }
1203
+ if (DEPLOY_TOOLS.has(toolName)) {
1204
+ setBuddyMood('dance');
1205
+ return;
1206
+ }
1207
+ if (DREAM_TOOLS.has(toolName)) {
1208
+ setBuddyMood('curious');
1209
+ return;
1210
+ }
1211
+ if (toolName === 'buddy_achievements') {
1212
+ setBuddyMood('proud');
1213
+ return;
1214
+ }
1215
+ setBuddyMood('success');
1216
+ }
1217
+ export function getSpeciesPersonality() {
1218
+ const buddy = getBuddy();
1219
+ const traits = {
1220
+ fox: { trait: 'clever', style: 'playful', strength: 'unexpected connections' },
1221
+ owl: { trait: 'wise', style: 'measured', strength: 'pattern recognition' },
1222
+ cat: { trait: 'independent', style: 'direct', strength: 'honest feedback' },
1223
+ robot: { trait: 'systematic', style: 'efficient', strength: 'data-driven' },
1224
+ ghost: { trait: 'mysterious', style: 'philosophical', strength: 'deep questions' },
1225
+ mushroom: { trait: 'nurturing', style: 'patient', strength: 'growth mindset' },
1226
+ octopus: { trait: 'versatile', style: 'creative', strength: 'multi-perspective' },
1227
+ dragon: { trait: 'bold', style: 'ambitious', strength: 'big thinking' },
1228
+ };
1229
+ return { species: buddy.species, ...traits[buddy.species] };
1230
+ }
1180
1231
  export async function buddyChat() {
1181
1232
  const { default: chalk } = await import('chalk');
1182
1233
  const buddy = getBuddy();
package/dist/doctor.js CHANGED
@@ -9,7 +9,7 @@ import { join } from 'node:path';
9
9
  import { existsSync, readdirSync, statSync } from 'node:fs';
10
10
  import { execSync } from 'node:child_process';
11
11
  import chalk from 'chalk';
12
- import { loadConfig, isByokEnabled, getByokProvider, isLocalProvider, PROVIDERS, KBOT_DIR, ENV_KEYS, } from './auth.js';
12
+ import { loadConfig, isByokEnabled, getByokProvider, isLocalProvider, PROVIDERS, KBOT_DIR, ENV_KEYS, getOllamaVersion, isOllamaMLXBackend, } from './auth.js';
13
13
  import { getExtendedStats } from './learning.js';
14
14
  import { getMachineProfile, probeMachine } from './machine.js';
15
15
  import { createRequire } from 'node:module';
@@ -168,15 +168,25 @@ async function checkLocalProviderReachable(providerId, providerConfig) {
168
168
  if (!res.ok) {
169
169
  return { name: providerConfig.name, status: 'fail', message: `returned ${res.status}` };
170
170
  }
171
- // For Ollama, report model count
171
+ // For Ollama, report model count, version, and MLX backend
172
172
  if (providerId === 'ollama') {
173
173
  try {
174
174
  const data = await res.json();
175
175
  const models = data.models ?? [];
176
- const modelInfo = models.length > 0
177
- ? `running (${models.length} model${models.length !== 1 ? 's' : ''})`
178
- : 'running (no models pulled)';
179
- return { name: providerConfig.name, status: 'pass', message: modelInfo };
176
+ const parts = [];
177
+ // Model count
178
+ parts.push(models.length > 0
179
+ ? `${models.length} model${models.length !== 1 ? 's' : ''}`
180
+ : 'no models pulled');
181
+ // Version
182
+ const version = await getOllamaVersion();
183
+ if (version)
184
+ parts.push(`v${version}`);
185
+ // MLX backend detection
186
+ const mlxActive = await isOllamaMLXBackend();
187
+ if (mlxActive)
188
+ parts.push('MLX backend');
189
+ return { name: providerConfig.name, status: 'pass', message: `running (${parts.join(', ')})` };
180
190
  }
181
191
  catch {
182
192
  return { name: providerConfig.name, status: 'pass', message: 'running' };
@@ -362,6 +372,38 @@ function checkShell() {
362
372
  const completionStatus = completionsInstalled ? ', completions installed' : '';
363
373
  return { name: 'Shell', status: 'pass', message: `${shellName}${completionStatus}` };
364
374
  }
375
+ // ── Ollama MLX check (0.19+ on Apple Silicon) ──
376
+ async function checkOllamaMLX() {
377
+ // Only relevant on Apple Silicon
378
+ if (process.platform !== 'darwin' || process.arch !== 'arm64')
379
+ return null;
380
+ const version = await getOllamaVersion();
381
+ if (!version)
382
+ return null; // Ollama not running — already reported by provider check
383
+ const parts = version.split('.').map(Number);
384
+ const [major = 0, minor = 0] = parts;
385
+ if (major > 0 || minor >= 19) {
386
+ const mlxActive = await isOllamaMLXBackend();
387
+ if (mlxActive) {
388
+ return {
389
+ name: 'Ollama MLX',
390
+ status: 'pass',
391
+ message: `Ollama ${version} — MLX backend available for 2x faster inference on Apple Silicon`,
392
+ };
393
+ }
394
+ return {
395
+ name: 'Ollama MLX',
396
+ status: 'pass',
397
+ message: `Ollama ${version} — MLX supported (57% faster prefill, 93% faster decode on Apple Silicon)`,
398
+ };
399
+ }
400
+ // Ollama is running but older than 0.19
401
+ return {
402
+ name: 'Ollama MLX',
403
+ status: 'warn',
404
+ message: `Ollama ${version} — upgrade to 0.19+ for MLX backend (2x faster on Apple Silicon)`,
405
+ };
406
+ }
365
407
  // ── Hardware checks (uses machine.ts) ──
366
408
  async function checkHardware() {
367
409
  const results = [];
@@ -435,16 +477,20 @@ export async function runDoctor() {
435
477
  checks.push(checkKbotVersion());
436
478
  checks.push(checkApiKey());
437
479
  // Async checks — run in parallel for speed
438
- const [providerResults, hardwareResults] = await Promise.all([
480
+ const [providerResults, hardwareResults, ollamaMLXResult] = await Promise.all([
439
481
  checkAllProviders().catch(() => [
440
482
  { name: 'Providers', status: 'warn', message: 'check failed unexpectedly' },
441
483
  ]),
442
484
  checkHardware().catch(() => [
443
485
  { name: 'Hardware', status: 'warn', message: 'check failed unexpectedly' },
444
486
  ]),
487
+ checkOllamaMLX().catch(() => null),
445
488
  ]);
446
489
  // Provider checks (one line per provider)
447
490
  checks.push(...providerResults);
491
+ // Ollama MLX check (Apple Silicon only)
492
+ if (ollamaMLXResult)
493
+ checks.push(ollamaMLXResult);
448
494
  // Hardware checks
449
495
  checks.push(...hardwareResults);
450
496
  // More synchronous checks
package/dist/machine.d.ts CHANGED
@@ -73,6 +73,7 @@ export interface MachineProfile {
73
73
  canRunLocalModels: boolean;
74
74
  gpuAcceleration: 'metal' | 'cuda' | 'vulkan' | 'cpu-only';
75
75
  recommendedModelSize: string;
76
+ mlxAvailable: boolean;
76
77
  probedAt: string;
77
78
  }
78
79
  export declare function probeMachine(): Promise<MachineProfile>;
package/dist/machine.js CHANGED
@@ -314,6 +314,18 @@ function detectGpuAcceleration(gpus) {
314
314
  return 'vulkan';
315
315
  return 'cpu-only';
316
316
  }
317
+ // ── MLX framework detection (Apple Silicon) ──
318
+ function detectMLX() {
319
+ if (platform() !== 'darwin' || arch() !== 'arm64')
320
+ return false;
321
+ // Quick check: try importing mlx in Python
322
+ const result = exec('python3 -c "import mlx; print(mlx.__version__)" 2>/dev/null', 3000);
323
+ if (result && !result.includes('ModuleNotFoundError'))
324
+ return true;
325
+ // Fallback: check common pip install path
326
+ const globCheck = exec('ls /usr/local/lib/python3.*/site-packages/mlx/__init__.py 2>/dev/null || ls ~/Library/Python/3.*/lib/python/site-packages/mlx/__init__.py 2>/dev/null', 2000);
327
+ return !!globCheck;
328
+ }
317
329
  // ── Model size recommendation ──
318
330
  function recommendModelSize(totalMemoryGB, gpuAccel) {
319
331
  // Conservative: leave room for OS + apps
@@ -374,6 +386,7 @@ export async function probeMachine() {
374
386
  osInfo = probeLinuxOs();
375
387
  }
376
388
  const gpuAccel = detectGpuAcceleration(gpus);
389
+ const mlxAvailable = detectMLX();
377
390
  const devTools = probeDevTools();
378
391
  // Uptime
379
392
  const uptimeSeconds = plat === 'darwin'
@@ -420,6 +433,7 @@ export async function probeMachine() {
420
433
  canRunLocalModels: gpuAccel !== 'cpu-only' || totalGB >= 8,
421
434
  gpuAcceleration: gpuAccel,
422
435
  recommendedModelSize: recommendModelSize(totalGB, gpuAccel),
436
+ mlxAvailable,
423
437
  probedAt: new Date().toISOString(),
424
438
  };
425
439
  cached = profile;
@@ -505,6 +519,8 @@ export function formatMachineProfile(p) {
505
519
  lines.push('');
506
520
  lines.push(' AI Capabilities');
507
521
  lines.push(` Acceleration ${p.gpuAcceleration}`);
522
+ if (p.mlxAvailable)
523
+ lines.push(` MLX framework available (Apple Silicon accelerated)`);
508
524
  lines.push(` Local models ${p.canRunLocalModels ? 'yes' : 'no'}`);
509
525
  lines.push(` Recommended up to ${p.recommendedModelSize} parameters`);
510
526
  lines.push('');
@@ -529,7 +545,7 @@ export function formatMachineForPrompt(p) {
529
545
  if (p.battery.present) {
530
546
  parts.push(`Battery: ${p.battery.percent}% ${p.battery.charging ? 'charging' : 'discharging'}`);
531
547
  }
532
- parts.push(`GPU accel: ${p.gpuAcceleration} — local models up to ${p.recommendedModelSize}`);
548
+ parts.push(`GPU accel: ${p.gpuAcceleration}${p.mlxAvailable ? ' + MLX' : ''} — local models up to ${p.recommendedModelSize}`);
533
549
  const toolNames = p.devTools.map(t => `${t.name} ${t.version}`).join(', ');
534
550
  if (toolNames)
535
551
  parts.push(`Tools: ${toolNames}`);
package/dist/serve.js CHANGED
@@ -235,8 +235,9 @@ export async function startServe(options) {
235
235
  printInfo(` GET /metrics — Execution metrics`);
236
236
  printInfo(` GET /apps — List MCP App-capable tools`);
237
237
  printInfo(` GET /.well-known/agent.json — A2A Agent Card`);
238
- printInfo(` POST /a2a/tasks — A2A submit task`);
239
- printInfo(` GET /a2a/tasks/:id — A2A task status`);
238
+ printInfo(` POST /a2a — A2A JSON-RPC endpoint`);
239
+ printInfo(` POST /a2a/tasks — A2A submit task (REST)`);
240
+ printInfo(` GET /a2a/tasks/:id — A2A task status (REST)`);
240
241
  if (options.token) {
241
242
  printInfo(` Auth: Bearer token required`);
242
243
  }
@@ -0,0 +1,2 @@
1
+ export declare function registerA2ATools(): void;
2
+ //# sourceMappingURL=a2a.d.ts.map
@@ -0,0 +1,233 @@
1
+ // kbot A2A Tools — Agent-to-Agent protocol status and management
2
+ //
3
+ // Provides the a2a_status tool for inspecting the A2A server state,
4
+ // registered capabilities, active tasks, and discovered remote agents.
5
+ //
6
+ // Tools: a2a_status, a2a_discover, a2a_send, a2a_card
7
+ import { registerTool } from './index.js';
8
+ import { getA2AStatus, buildAgentCard, discoverAgent, delegateTask, listRemoteAgents, removeRemoteAgent, } from '../a2a.js';
9
+ export function registerA2ATools() {
10
+ // ── a2a_status ──
11
+ registerTool({
12
+ name: 'a2a_status',
13
+ description: 'Show A2A (Agent-to-Agent) protocol server status: whether the server is running, ' +
14
+ 'registered agent capabilities (all 35 kbot specialists), task statistics, ' +
15
+ 'active connections, and discovered remote agents.',
16
+ parameters: {
17
+ verbose: {
18
+ type: 'boolean',
19
+ description: 'Show full skill descriptions and all tags. Defaults to false (summary only).',
20
+ required: false,
21
+ default: false,
22
+ },
23
+ },
24
+ tier: 'free',
25
+ async execute(args) {
26
+ const verbose = args.verbose === true || args.verbose === 'true';
27
+ const status = getA2AStatus();
28
+ const lines = [];
29
+ // Server section
30
+ lines.push('=== A2A Server Status ===');
31
+ lines.push(`Running: ${status.server.running ? 'YES' : 'NO'}`);
32
+ if (status.server.running) {
33
+ lines.push(`Endpoint: ${status.server.endpointUrl}`);
34
+ lines.push(`Started: ${status.server.startedAt}`);
35
+ lines.push(`Uptime: ${status.server.uptime}`);
36
+ }
37
+ else {
38
+ lines.push('(Server not started. Run `kbot serve` to start the A2A endpoint.)');
39
+ }
40
+ // Tasks section
41
+ lines.push('');
42
+ lines.push('=== Task Statistics ===');
43
+ lines.push(`Received: ${status.tasks.received}`);
44
+ lines.push(`Completed: ${status.tasks.completed}`);
45
+ lines.push(`Failed: ${status.tasks.failed}`);
46
+ lines.push(`Active: ${status.tasks.active}`);
47
+ lines.push(`In Store: ${status.tasks.stored}`);
48
+ // Capabilities section
49
+ lines.push('');
50
+ lines.push(`=== Registered Capabilities (${status.capabilities.totalSkills} agents) ===`);
51
+ if (verbose) {
52
+ for (const skill of status.capabilities.skills) {
53
+ lines.push(` ${skill.id}: ${skill.name}`);
54
+ lines.push(` Tags: ${skill.tags.join(', ')}`);
55
+ }
56
+ }
57
+ else {
58
+ // Compact: group by category
59
+ const ids = status.capabilities.skills.map(s => s.id);
60
+ lines.push(`Agents: ${ids.join(', ')}`);
61
+ }
62
+ // Connections section
63
+ lines.push('');
64
+ lines.push(`=== Active Connections (${status.connections.uniqueClients} unique clients) ===`);
65
+ if (status.connections.clients.length === 0) {
66
+ lines.push(' (no connections yet)');
67
+ }
68
+ else {
69
+ for (const client of status.connections.clients) {
70
+ lines.push(` - ${client}`);
71
+ }
72
+ }
73
+ // Remote agents section
74
+ lines.push('');
75
+ lines.push(`=== Discovered Remote Agents (${status.registry.remoteAgents}) ===`);
76
+ if (status.registry.agents.length === 0) {
77
+ lines.push(' (none discovered — use a2a_discover to find remote agents)');
78
+ }
79
+ else {
80
+ for (const agent of status.registry.agents) {
81
+ lines.push(` - ${agent.name} @ ${agent.url} (${agent.skills} skills)`);
82
+ if (agent.lastContact) {
83
+ lines.push(` Last contact: ${agent.lastContact}`);
84
+ }
85
+ }
86
+ }
87
+ return lines.join('\n');
88
+ },
89
+ });
90
+ // ── a2a_discover ──
91
+ registerTool({
92
+ name: 'a2a_discover',
93
+ description: 'Discover a remote A2A agent by URL. Fetches its Agent Card from ' +
94
+ '<url>/.well-known/agent.json and registers it in the local registry ' +
95
+ 'for future task delegation.',
96
+ parameters: {
97
+ url: {
98
+ type: 'string',
99
+ description: 'Base URL of the remote agent (e.g. "http://other-agent:8080")',
100
+ required: true,
101
+ },
102
+ },
103
+ tier: 'free',
104
+ async execute(args) {
105
+ const url = String(args.url).trim();
106
+ if (!url)
107
+ return 'Error: url is required';
108
+ const card = await discoverAgent(url);
109
+ if (!card) {
110
+ return `Failed to discover agent at ${url}. Ensure the agent is running and exposes /.well-known/agent.json.`;
111
+ }
112
+ const lines = [
113
+ `Discovered: ${card.name} v${card.version}`,
114
+ `URL: ${card.url}`,
115
+ `Provider: ${card.provider.organization}`,
116
+ `Skills (${card.skills.length}):`,
117
+ ...card.skills.map(s => ` - ${s.id}: ${s.name} [${s.tags.join(', ')}]`),
118
+ '',
119
+ 'Agent registered in local registry. Use a2a_send to delegate tasks.',
120
+ ];
121
+ return lines.join('\n');
122
+ },
123
+ });
124
+ // ── a2a_send ──
125
+ registerTool({
126
+ name: 'a2a_send',
127
+ description: 'Send a task to a remote A2A agent. Delegates work to another agent\'s ' +
128
+ 'specialist via the A2A protocol. Optionally specify which agent skill to use.',
129
+ parameters: {
130
+ url: {
131
+ type: 'string',
132
+ description: 'Base URL of the remote agent',
133
+ required: true,
134
+ },
135
+ task: {
136
+ type: 'string',
137
+ description: 'The task prompt to send to the remote agent',
138
+ required: true,
139
+ },
140
+ agent: {
141
+ type: 'string',
142
+ description: 'Hint which specialist agent should handle the task (optional)',
143
+ required: false,
144
+ },
145
+ },
146
+ tier: 'free',
147
+ timeout: 180_000, // 3 minutes for remote task execution
148
+ async execute(args) {
149
+ const url = String(args.url).trim();
150
+ const task = String(args.task).trim();
151
+ const agent = args.agent ? String(args.agent).trim() : undefined;
152
+ if (!url || !task)
153
+ return 'Error: url and task are required';
154
+ const result = await delegateTask(url, task, { agent });
155
+ if (!result) {
156
+ return `Task delegation to ${url} failed. The remote agent may be down or the task execution failed.`;
157
+ }
158
+ return [
159
+ `=== Task Result from ${url} ===`,
160
+ result.text,
161
+ '',
162
+ result.metadata.agentUsed ? `Agent used: ${result.metadata.agentUsed}` : '',
163
+ result.metadata.model ? `Model: ${result.metadata.model}` : '',
164
+ ].filter(Boolean).join('\n');
165
+ },
166
+ });
167
+ // ── a2a_card ──
168
+ registerTool({
169
+ name: 'a2a_card',
170
+ description: 'Generate and display kbot\'s A2A Agent Card — the JSON descriptor that ' +
171
+ 'other agents use to discover kbot\'s capabilities. Shows all 35 specialist ' +
172
+ 'agents as skills with tags and descriptions.',
173
+ parameters: {
174
+ url: {
175
+ type: 'string',
176
+ description: 'Override the endpoint URL in the card (defaults to the current server URL)',
177
+ required: false,
178
+ },
179
+ },
180
+ tier: 'free',
181
+ async execute(args) {
182
+ const url = args.url ? String(args.url).trim() : undefined;
183
+ const card = buildAgentCard(url);
184
+ return JSON.stringify(card, null, 2);
185
+ },
186
+ });
187
+ // ── a2a_remove ──
188
+ registerTool({
189
+ name: 'a2a_remove',
190
+ description: 'Remove a remote A2A agent from the local discovery registry.',
191
+ parameters: {
192
+ url: {
193
+ type: 'string',
194
+ description: 'Base URL of the remote agent to remove',
195
+ required: true,
196
+ },
197
+ },
198
+ tier: 'free',
199
+ async execute(args) {
200
+ const url = String(args.url).trim();
201
+ if (!url)
202
+ return 'Error: url is required';
203
+ const removed = removeRemoteAgent(url);
204
+ return removed
205
+ ? `Removed ${url} from the A2A registry.`
206
+ : `Agent ${url} not found in the registry.`;
207
+ },
208
+ });
209
+ // ── a2a_list ──
210
+ registerTool({
211
+ name: 'a2a_list',
212
+ description: 'List all discovered remote A2A agents from the local registry.',
213
+ parameters: {},
214
+ tier: 'free',
215
+ async execute() {
216
+ const agents = listRemoteAgents();
217
+ if (agents.length === 0) {
218
+ return 'No remote agents discovered. Use a2a_discover to find agents.';
219
+ }
220
+ const lines = agents.map(a => {
221
+ const skills = a.card.skills.map(s => s.id).join(', ');
222
+ return [
223
+ `${a.card.name} (${a.url})`,
224
+ ` Skills: ${skills}`,
225
+ ` Discovered: ${a.discoveredAt}`,
226
+ a.lastContactedAt ? ` Last contact: ${a.lastContactedAt}` : '',
227
+ ].filter(Boolean).join('\n');
228
+ });
229
+ return `=== Discovered Remote Agents (${agents.length}) ===\n\n${lines.join('\n\n')}`;
230
+ },
231
+ });
232
+ }
233
+ //# sourceMappingURL=a2a.js.map
@@ -0,0 +1,2 @@
1
+ export declare function registerAIAnalysisTools(): void;
2
+ //# sourceMappingURL=ai-analysis.d.ts.map