@kernel.chat/kbot 3.69.1 → 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/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