@kernel.chat/kbot 3.69.1 → 3.71.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/a2a.d.ts CHANGED
@@ -69,7 +69,13 @@ export interface RemoteAgent {
69
69
  discoveredAt: string;
70
70
  lastContactedAt?: string;
71
71
  }
72
- /** Build the Agent Card for this kbot instance */
72
+ /** Build the Agent Card for this kbot instance.
73
+ *
74
+ * Exposes all 35 kbot agents as A2A skills — 26 specialists from
75
+ * the SPECIALISTS registry plus 9 preset agents from the matrix system.
76
+ * Each skill includes tags for capability-based discovery and optional
77
+ * example prompts.
78
+ */
73
79
  export declare function buildAgentCard(endpointUrl?: string): AgentCard;
74
80
  export interface A2ARouteOptions {
75
81
  /** Port the server is running on (for Agent Card URL) */
@@ -79,12 +85,51 @@ export interface A2ARouteOptions {
79
85
  /** Bearer token for authentication (optional) */
80
86
  token?: string;
81
87
  }
88
+ /** Get a snapshot of the A2A server status for the a2a_status tool */
89
+ export declare function getA2AStatus(): {
90
+ server: {
91
+ running: boolean;
92
+ startedAt: string | null;
93
+ endpointUrl: string | null;
94
+ uptime: string | null;
95
+ };
96
+ tasks: {
97
+ received: number;
98
+ completed: number;
99
+ failed: number;
100
+ active: number;
101
+ stored: number;
102
+ };
103
+ capabilities: {
104
+ totalSkills: number;
105
+ skills: Array<{
106
+ id: string;
107
+ name: string;
108
+ tags: string[];
109
+ }>;
110
+ };
111
+ connections: {
112
+ uniqueClients: number;
113
+ clients: string[];
114
+ };
115
+ registry: {
116
+ remoteAgents: number;
117
+ agents: Array<{
118
+ url: string;
119
+ name: string;
120
+ skills: number;
121
+ lastContact: string | null;
122
+ }>;
123
+ };
124
+ };
82
125
  /**
83
- * Mount A2A protocol routes onto an existing HTTP server's request handler.
126
+ * Create the A2A request handler.
127
+ *
128
+ * Supports two interfaces:
129
+ * 1. **JSON-RPC** (A2A spec) — POST to `/a2a` with JSON-RPC envelope
130
+ * 2. **REST** (backward-compatible) — GET/POST to `/a2a/tasks`, `/a2a/tasks/:id`, etc.
84
131
  *
85
- * Returns a request handler that should be called from the server's main
86
- * request listener. Returns `true` if the request was handled by A2A routes,
87
- * `false` if it should be passed to the next handler.
132
+ * Agent Card discovery is always at `GET /.well-known/agent.json`.
88
133
  */
89
134
  export declare function createA2AHandler(options?: A2ARouteOptions): (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
90
135
  /**
package/dist/a2a.js CHANGED
@@ -45,74 +45,141 @@ function pruneTasks() {
45
45
  }
46
46
  }
47
47
  // ── Skill mapping ──
48
+ /** Tag map for all agents — specialists, preset agents, and domain experts.
49
+ * Used to populate A2A skill tags for discovery by external agents. */
50
+ const AGENT_TAG_MAP = {
51
+ // Core specialists
52
+ kernel: ['general', 'assistant', 'coordination'],
53
+ researcher: ['research', 'fact-checking', 'synthesis'],
54
+ coder: ['programming', 'code-generation', 'debugging', 'refactoring'],
55
+ writer: ['writing', 'documentation', 'content-creation'],
56
+ analyst: ['analysis', 'strategy', 'evaluation'],
57
+ // Extended specialists
58
+ aesthete: ['design', 'ui-ux', 'css', 'accessibility'],
59
+ guardian: ['security', 'vulnerability-scanning', 'owasp'],
60
+ curator: ['knowledge-management', 'documentation', 'indexing'],
61
+ strategist: ['business-strategy', 'roadmapping', 'competitive-analysis'],
62
+ // Domain specialists
63
+ infrastructure: ['devops', 'ci-cd', 'containers', 'cloud'],
64
+ quant: ['data-science', 'statistics', 'quantitative-analysis'],
65
+ investigator: ['deep-research', 'root-cause-analysis', 'forensics'],
66
+ oracle: ['predictions', 'trend-analysis', 'forecasting'],
67
+ chronist: ['history', 'timelines', 'changelog'],
68
+ sage: ['philosophy', 'wisdom', 'mental-models'],
69
+ communicator: ['communication', 'messaging', 'presentations'],
70
+ adapter: ['translation', 'format-conversion', 'migration'],
71
+ producer: ['music-production', 'ableton', 'daw', 'audio', 'midi'],
72
+ scientist: ['science', 'biology', 'chemistry', 'physics', 'earth-science', 'mathematics'],
73
+ immune: ['self-audit', 'bug-finding', 'security-hardening', 'code-quality'],
74
+ neuroscientist: ['neuroscience', 'brain', 'eeg', 'cognitive-science', 'neuroimaging'],
75
+ social_scientist: ['social-science', 'psychometrics', 'game-theory', 'econometrics', 'survey-design'],
76
+ philosopher: ['philosophy', 'logic', 'ethics', 'argumentation'],
77
+ epidemiologist: ['epidemiology', 'public-health', 'disease-modeling', 'surveillance'],
78
+ linguist: ['linguistics', 'phonetics', 'typology', 'corpus-analysis', 'nlp'],
79
+ historian: ['history', 'archival-research', 'digital-humanities', 'timelines'],
80
+ // Preset agents
81
+ hacker: ['offensive-security', 'penetration-testing', 'ctf', 'red-team'],
82
+ operator: ['automation', 'orchestration', 'task-execution', 'planning'],
83
+ dreamer: ['creative', 'worldbuilding', 'imagination', 'dream-interpretation'],
84
+ creative: ['generative-art', 'creative-coding', 'shaders', 'music', 'procedural-generation'],
85
+ developer: ['kbot', 'self-improvement', 'typescript', 'tooling'],
86
+ replit: ['replit', 'deployment', 'ai-integration', 'rapid-prototyping'],
87
+ gamedev: ['game-development', 'game-design', 'godot', 'unity', 'game-feel'],
88
+ playtester: ['game-testing', 'qa', 'game-feel', 'ux-testing', 'difficulty-analysis'],
89
+ trader: ['trading', 'markets', 'technical-analysis', 'portfolio', 'defi'],
90
+ };
48
91
  /** Map specialist definitions to A2A skills */
49
92
  function specialistToSkill(id, def) {
50
- const tagMap = {
51
- kernel: ['general', 'assistant', 'coordination'],
52
- researcher: ['research', 'fact-checking', 'synthesis'],
53
- coder: ['programming', 'code-generation', 'debugging', 'refactoring'],
54
- writer: ['writing', 'documentation', 'content-creation'],
55
- analyst: ['analysis', 'strategy', 'evaluation'],
56
- aesthete: ['design', 'ui-ux', 'css', 'accessibility'],
57
- guardian: ['security', 'vulnerability-scanning', 'owasp'],
58
- curator: ['knowledge-management', 'documentation', 'indexing'],
59
- strategist: ['business-strategy', 'roadmapping', 'competitive-analysis'],
60
- infrastructure: ['devops', 'ci-cd', 'containers', 'cloud'],
61
- quant: ['data-science', 'statistics', 'quantitative-analysis'],
62
- investigator: ['deep-research', 'root-cause-analysis', 'forensics'],
63
- oracle: ['predictions', 'trend-analysis', 'forecasting'],
64
- chronist: ['history', 'timelines', 'changelog'],
65
- sage: ['philosophy', 'wisdom', 'mental-models'],
66
- communicator: ['communication', 'messaging', 'presentations'],
67
- adapter: ['translation', 'format-conversion', 'migration'],
68
- };
69
93
  return {
70
94
  id,
71
95
  name: def.name,
72
96
  description: def.prompt.split('\n')[0].replace(/^You are (?:an? )?/, ''),
73
- tags: tagMap[id] || [id],
97
+ tags: AGENT_TAG_MAP[id] || [id],
74
98
  };
75
99
  }
76
- /** Additional built-in agent skills (preset agents beyond the 17 specialists) */
77
- const EXTRA_SKILLS = [
100
+ /** Skill descriptions for preset agents not in SPECIALISTS.
101
+ * These come from matrix.ts BUILTIN_AGENTS and PRESETS. */
102
+ const PRESET_AGENT_SKILLS = [
78
103
  {
79
104
  id: 'hacker',
80
105
  name: 'Hacker',
81
- description: 'Offensive security specialist and CTF solver — red team analysis',
82
- tags: ['offensive-security', 'penetration-testing', 'ctf'],
106
+ description: 'Offensive security specialist and CTF solver — red team analysis, exploit chains, and remediation',
107
+ tags: AGENT_TAG_MAP['hacker'],
108
+ examples: ['Find auth bypass in this API', 'Solve this CTF challenge', 'Red team our deployment'],
83
109
  },
84
110
  {
85
111
  id: 'operator',
86
112
  name: 'Operator',
87
- description: 'Autonomous executor — plans, executes, verifies, and reports',
88
- tags: ['automation', 'orchestration', 'task-execution'],
113
+ description: 'Autonomous executor — plans, decomposes, executes, verifies, and reports multi-step tasks',
114
+ tags: AGENT_TAG_MAP['operator'],
115
+ examples: ['Refactor this module and update all tests', 'Set up CI/CD for this repo'],
89
116
  },
90
117
  {
91
118
  id: 'dreamer',
92
119
  name: 'Dreamer',
93
- description: 'Liminal space explorer — dream interpretation, worldbuilding, vision engineering',
94
- tags: ['creative', 'worldbuilding', 'imagination'],
120
+ description: 'Liminal space explorer — dream interpretation, worldbuilding, and vision engineering',
121
+ tags: AGENT_TAG_MAP['dreamer'],
122
+ examples: ['Interpret this dream', 'Build a world with these constraints'],
95
123
  },
96
124
  {
97
125
  id: 'creative',
98
126
  name: 'Creative',
99
- description: 'Generative art, creative coding, shaders, procedural generation',
100
- tags: ['generative-art', 'creative-coding', 'shaders', 'music'],
127
+ description: 'Generative art, creative coding, shaders, procedural generation, and computational aesthetics',
128
+ tags: AGENT_TAG_MAP['creative'],
129
+ examples: ['Generate a p5.js particle system', 'Write a GLSL shader for this effect'],
101
130
  },
102
131
  {
103
132
  id: 'developer',
104
133
  name: 'Developer',
105
- description: 'kbot self-improvement specialist — builds and extends kbot itself',
106
- tags: ['kbot', 'self-improvement', 'typescript', 'tooling'],
134
+ description: 'kbot self-improvement specialist — builds, extends, and optimizes kbot itself',
135
+ tags: AGENT_TAG_MAP['developer'],
136
+ examples: ['Add a new tool to kbot', 'Optimize the agent routing system'],
137
+ },
138
+ {
139
+ id: 'replit',
140
+ name: 'Replit',
141
+ description: 'Replit specialist — builds, integrates, and deploys AI-powered systems on Replit',
142
+ tags: AGENT_TAG_MAP['replit'],
143
+ examples: ['Deploy this app on Replit', 'Set up Replit AI integration'],
144
+ },
145
+ {
146
+ id: 'gamedev',
147
+ name: 'Game Developer',
148
+ description: 'Game development specialist — game design, Godot/Unity, game feel, systems design, and rapid prototyping',
149
+ tags: AGENT_TAG_MAP['gamedev'],
150
+ examples: ['Design a combat system', 'Build a Godot platformer prototype'],
151
+ },
152
+ {
153
+ id: 'playtester',
154
+ name: 'Playtester',
155
+ description: 'Game QA and playtesting specialist — difficulty analysis, UX evaluation, game feel critique',
156
+ tags: AGENT_TAG_MAP['playtester'],
157
+ examples: ['Evaluate this game design', 'Test the difficulty curve'],
158
+ },
159
+ {
160
+ id: 'trader',
161
+ name: 'Trader',
162
+ description: 'Trading and market analysis specialist — technical analysis, portfolio management, DeFi, and quantitative strategies',
163
+ tags: AGENT_TAG_MAP['trader'],
164
+ examples: ['Analyze this stock chart', 'Build a portfolio rebalancing strategy'],
107
165
  },
108
166
  ];
109
167
  // ── Agent Card ──
110
- /** Build the Agent Card for this kbot instance */
168
+ /** Build the Agent Card for this kbot instance.
169
+ *
170
+ * Exposes all 35 kbot agents as A2A skills — 26 specialists from
171
+ * the SPECIALISTS registry plus 9 preset agents from the matrix system.
172
+ * Each skill includes tags for capability-based discovery and optional
173
+ * example prompts.
174
+ */
111
175
  export function buildAgentCard(endpointUrl) {
112
176
  const url = endpointUrl || `http://localhost:${DEFAULT_PORT}`;
177
+ // Deduplicate: PRESET_AGENT_SKILLS may overlap with SPECIALISTS keys
178
+ const specialistIds = new Set(Object.keys(SPECIALISTS));
179
+ const deduplicatedPresets = PRESET_AGENT_SKILLS.filter(s => !specialistIds.has(s.id));
113
180
  const skills = [
114
181
  ...Object.entries(SPECIALISTS).map(([id, def]) => specialistToSkill(id, def)),
115
- ...EXTRA_SKILLS,
182
+ ...deduplicatedPresets,
116
183
  ];
117
184
  return {
118
185
  name: 'kbot',
@@ -226,21 +293,180 @@ function readBody(req) {
226
293
  req.on('error', reject);
227
294
  });
228
295
  }
296
+ const serverState = {
297
+ running: false,
298
+ startedAt: null,
299
+ endpointUrl: null,
300
+ tasksReceived: 0,
301
+ tasksCompleted: 0,
302
+ tasksFailed: 0,
303
+ activeConnections: new Set(),
304
+ };
305
+ /** Record an incoming connection (caller agent URL or IP) */
306
+ function trackConnection(req) {
307
+ const forwarded = req.headers['x-forwarded-for'];
308
+ const ip = typeof forwarded === 'string' ? forwarded.split(',')[0].trim() : req.socket.remoteAddress || 'unknown';
309
+ const userAgent = req.headers['user-agent'] || 'unknown';
310
+ serverState.activeConnections.add(`${ip} (${userAgent})`);
311
+ }
312
+ /** Get a snapshot of the A2A server status for the a2a_status tool */
313
+ export function getA2AStatus() {
314
+ const card = buildAgentCard(serverState.endpointUrl || undefined);
315
+ const registry = loadRegistry();
316
+ const activeTasks = [...tasks.values()].filter(t => t.status.state === 'working' || t.status.state === 'submitted');
317
+ let uptime = null;
318
+ if (serverState.startedAt) {
319
+ const ms = Date.now() - new Date(serverState.startedAt).getTime();
320
+ const hours = Math.floor(ms / 3_600_000);
321
+ const minutes = Math.floor((ms % 3_600_000) / 60_000);
322
+ const seconds = Math.floor((ms % 60_000) / 1_000);
323
+ uptime = `${hours}h ${minutes}m ${seconds}s`;
324
+ }
325
+ return {
326
+ server: {
327
+ running: serverState.running,
328
+ startedAt: serverState.startedAt,
329
+ endpointUrl: serverState.endpointUrl,
330
+ uptime,
331
+ },
332
+ tasks: {
333
+ received: serverState.tasksReceived,
334
+ completed: serverState.tasksCompleted,
335
+ failed: serverState.tasksFailed,
336
+ active: activeTasks.length,
337
+ stored: tasks.size,
338
+ },
339
+ capabilities: {
340
+ totalSkills: card.skills.length,
341
+ skills: card.skills.map(s => ({ id: s.id, name: s.name, tags: s.tags })),
342
+ },
343
+ connections: {
344
+ uniqueClients: serverState.activeConnections.size,
345
+ clients: [...serverState.activeConnections],
346
+ },
347
+ registry: {
348
+ remoteAgents: Object.keys(registry).length,
349
+ agents: Object.values(registry).map(r => ({
350
+ url: r.url,
351
+ name: r.card.name,
352
+ skills: r.card.skills.length,
353
+ lastContact: r.lastContactedAt || null,
354
+ })),
355
+ },
356
+ };
357
+ }
358
+ /** JSON-RPC error codes per the spec */
359
+ const JSONRPC_PARSE_ERROR = -32700;
360
+ const JSONRPC_INVALID_REQUEST = -32600;
361
+ const JSONRPC_METHOD_NOT_FOUND = -32601;
362
+ const JSONRPC_INVALID_PARAMS = -32602;
363
+ const JSONRPC_INTERNAL_ERROR = -32603;
364
+ const JSONRPC_TASK_NOT_FOUND = -32001;
365
+ const JSONRPC_TASK_NOT_CANCELABLE = -32002;
366
+ function jsonRpcSuccess(id, result) {
367
+ return { jsonrpc: '2.0', id, result };
368
+ }
369
+ function jsonRpcError(id, code, message, data) {
370
+ return { jsonrpc: '2.0', id, error: { code, message, ...(data !== undefined ? { data } : {}) } };
371
+ }
372
+ /**
373
+ * Handle a JSON-RPC request per the A2A protocol spec.
374
+ *
375
+ * Supported methods:
376
+ * - tasks/send — submit a task (sync execution)
377
+ * - tasks/get — get task status and result
378
+ * - tasks/cancel — cancel a running task
379
+ * - tasks/sendSubscribe — submit a task (async, returns immediately)
380
+ */
381
+ async function handleJsonRpc(rpcReq) {
382
+ const { id, method, params } = rpcReq;
383
+ switch (method) {
384
+ case 'tasks/send': {
385
+ const message = params?.message;
386
+ if (!message?.parts || !message?.role) {
387
+ return jsonRpcError(id, JSONRPC_INVALID_PARAMS, 'Invalid params: expected { message: { role, parts } }');
388
+ }
389
+ const metadata = params?.metadata;
390
+ const task = createTask(message, metadata);
391
+ serverState.tasksReceived++;
392
+ const completed = await executeTask(task);
393
+ if (completed.status.state === 'completed')
394
+ serverState.tasksCompleted++;
395
+ if (completed.status.state === 'failed')
396
+ serverState.tasksFailed++;
397
+ return jsonRpcSuccess(id, taskToResponse(completed));
398
+ }
399
+ case 'tasks/sendSubscribe': {
400
+ const message = params?.message;
401
+ if (!message?.parts || !message?.role) {
402
+ return jsonRpcError(id, JSONRPC_INVALID_PARAMS, 'Invalid params: expected { message: { role, parts } }');
403
+ }
404
+ const metadata = params?.metadata;
405
+ const task = createTask(message, metadata);
406
+ serverState.tasksReceived++;
407
+ // Async: return immediately, execute in background
408
+ executeTask(task).then(() => {
409
+ if (task.status.state === 'completed')
410
+ serverState.tasksCompleted++;
411
+ if (task.status.state === 'failed')
412
+ serverState.tasksFailed++;
413
+ }).catch(() => {
414
+ task.status = { state: 'failed', message: 'Background execution failed', timestamp: new Date().toISOString() };
415
+ serverState.tasksFailed++;
416
+ });
417
+ return jsonRpcSuccess(id, taskToResponse(task));
418
+ }
419
+ case 'tasks/get': {
420
+ const taskId = params?.id;
421
+ if (!taskId) {
422
+ return jsonRpcError(id, JSONRPC_INVALID_PARAMS, 'Invalid params: expected { id: string }');
423
+ }
424
+ const task = tasks.get(taskId);
425
+ if (!task) {
426
+ return jsonRpcError(id, JSONRPC_TASK_NOT_FOUND, `Task ${taskId} not found`);
427
+ }
428
+ return jsonRpcSuccess(id, taskToResponse(task));
429
+ }
430
+ case 'tasks/cancel': {
431
+ const taskId = params?.id;
432
+ if (!taskId) {
433
+ return jsonRpcError(id, JSONRPC_INVALID_PARAMS, 'Invalid params: expected { id: string }');
434
+ }
435
+ const task = tasks.get(taskId);
436
+ if (!task) {
437
+ return jsonRpcError(id, JSONRPC_TASK_NOT_FOUND, `Task ${taskId} not found`);
438
+ }
439
+ if (task.status.state === 'completed' || task.status.state === 'failed') {
440
+ return jsonRpcError(id, JSONRPC_TASK_NOT_CANCELABLE, `Cannot cancel task in '${task.status.state}' state`);
441
+ }
442
+ task.status = { state: 'canceled', timestamp: new Date().toISOString() };
443
+ return jsonRpcSuccess(id, taskToResponse(task));
444
+ }
445
+ default:
446
+ return jsonRpcError(id, JSONRPC_METHOD_NOT_FOUND, `Unknown method: ${method}`);
447
+ }
448
+ }
229
449
  /**
230
- * Mount A2A protocol routes onto an existing HTTP server's request handler.
450
+ * Create the A2A request handler.
451
+ *
452
+ * Supports two interfaces:
453
+ * 1. **JSON-RPC** (A2A spec) — POST to `/a2a` with JSON-RPC envelope
454
+ * 2. **REST** (backward-compatible) — GET/POST to `/a2a/tasks`, `/a2a/tasks/:id`, etc.
231
455
  *
232
- * Returns a request handler that should be called from the server's main
233
- * request listener. Returns `true` if the request was handled by A2A routes,
234
- * `false` if it should be passed to the next handler.
456
+ * Agent Card discovery is always at `GET /.well-known/agent.json`.
235
457
  */
236
458
  export function createA2AHandler(options = {}) {
237
459
  const baseUrl = options.endpointUrl || `http://localhost:${options.port || DEFAULT_PORT}`;
238
460
  const card = buildAgentCard(baseUrl);
461
+ // Mark server as running
462
+ serverState.running = true;
463
+ serverState.startedAt = new Date().toISOString();
464
+ serverState.endpointUrl = baseUrl;
239
465
  return async (req, res) => {
240
466
  const url = new URL(req.url || '/', `http://localhost`);
241
467
  const path = url.pathname;
242
468
  // CORS preflight for A2A routes
243
- if (req.method === 'OPTIONS' && (path === '/.well-known/agent.json' || path.startsWith('/a2a/'))) {
469
+ if (req.method === 'OPTIONS' && (path === '/.well-known/agent.json' || path.startsWith('/a2a'))) {
244
470
  res.setHeader('Access-Control-Allow-Origin', '*');
245
471
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
246
472
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
@@ -249,7 +475,7 @@ export function createA2AHandler(options = {}) {
249
475
  return true;
250
476
  }
251
477
  // Auth check for task endpoints
252
- if (options.token && path.startsWith('/a2a/')) {
478
+ if (options.token && path.startsWith('/a2a')) {
253
479
  const auth = req.headers.authorization;
254
480
  const bearerToken = auth?.startsWith('Bearer ') ? auth.slice(7) : null;
255
481
  const tokenBuf = Buffer.from(options.token);
@@ -261,11 +487,35 @@ export function createA2AHandler(options = {}) {
261
487
  }
262
488
  // GET /.well-known/agent.json — Agent Card discovery
263
489
  if (path === '/.well-known/agent.json' && req.method === 'GET') {
490
+ trackConnection(req);
264
491
  sendJson(res, 200, card);
265
492
  return true;
266
493
  }
267
- // POST /a2a/tasksSubmit a new task
494
+ // POST /a2a — JSON-RPC endpoint (A2A protocol spec)
495
+ if (path === '/a2a' && req.method === 'POST') {
496
+ trackConnection(req);
497
+ try {
498
+ const rawBody = await readBody(req);
499
+ const parsed = JSON.parse(rawBody);
500
+ // Validate JSON-RPC envelope
501
+ if (typeof parsed !== 'object' || parsed === null ||
502
+ parsed.jsonrpc !== '2.0' ||
503
+ typeof parsed.method !== 'string') {
504
+ sendJson(res, 200, jsonRpcError(parsed?.id ?? null, JSONRPC_INVALID_REQUEST, 'Invalid JSON-RPC request: expected { jsonrpc: "2.0", method: string, id: string|number }'));
505
+ return true;
506
+ }
507
+ const rpcReq = parsed;
508
+ const rpcRes = await handleJsonRpc(rpcReq);
509
+ sendJson(res, 200, rpcRes);
510
+ }
511
+ catch (err) {
512
+ sendJson(res, 200, jsonRpcError(null, JSONRPC_PARSE_ERROR, 'Parse error: ' + (err instanceof Error ? err.message : 'invalid JSON')));
513
+ }
514
+ return true;
515
+ }
516
+ // POST /a2a/tasks — Submit a new task (REST, backward-compatible)
268
517
  if (path === '/a2a/tasks' && req.method === 'POST') {
518
+ trackConnection(req);
269
519
  try {
270
520
  const body = JSON.parse(await readBody(req));
271
521
  if (!body.message || !body.message.parts || !body.message.role) {
@@ -273,19 +523,30 @@ export function createA2AHandler(options = {}) {
273
523
  return true;
274
524
  }
275
525
  const task = createTask(body.message, body.metadata);
526
+ serverState.tasksReceived++;
276
527
  // Check if caller wants synchronous execution
277
528
  const sync = url.searchParams.get('sync') === 'true';
278
529
  if (sync) {
279
530
  // Synchronous: execute and return completed task
280
531
  const completed = await executeTask(task);
532
+ if (completed.status.state === 'completed')
533
+ serverState.tasksCompleted++;
534
+ if (completed.status.state === 'failed')
535
+ serverState.tasksFailed++;
281
536
  sendJson(res, 200, taskToResponse(completed));
282
537
  }
283
538
  else {
284
539
  // Asynchronous: return immediately with submitted status, execute in background
285
540
  sendJson(res, 202, taskToResponse(task));
286
541
  // Fire and forget — task will be updated in the store
287
- executeTask(task).catch(() => {
542
+ executeTask(task).then(() => {
543
+ if (task.status.state === 'completed')
544
+ serverState.tasksCompleted++;
545
+ if (task.status.state === 'failed')
546
+ serverState.tasksFailed++;
547
+ }).catch(() => {
288
548
  task.status = { state: 'failed', message: 'Background execution failed', timestamp: new Date().toISOString() };
549
+ serverState.tasksFailed++;
289
550
  });
290
551
  }
291
552
  }
@@ -294,7 +555,7 @@ export function createA2AHandler(options = {}) {
294
555
  }
295
556
  return true;
296
557
  }
297
- // GET /a2a/tasks/:id — Get task status and result
558
+ // GET /a2a/tasks/:id — Get task status and result (REST)
298
559
  const taskStatusMatch = path.match(/^\/a2a\/tasks\/([a-f0-9-]+)$/);
299
560
  if (taskStatusMatch && req.method === 'GET') {
300
561
  const taskId = taskStatusMatch[1];
@@ -306,7 +567,7 @@ export function createA2AHandler(options = {}) {
306
567
  sendJson(res, 200, taskToResponse(task));
307
568
  return true;
308
569
  }
309
- // POST /a2a/tasks/:id/cancel — Cancel a task
570
+ // POST /a2a/tasks/:id/cancel — Cancel a task (REST)
310
571
  const taskCancelMatch = path.match(/^\/a2a\/tasks\/([a-f0-9-]+)\/cancel$/);
311
572
  if (taskCancelMatch && req.method === 'POST') {
312
573
  const taskId = taskCancelMatch[1];
package/dist/auth.d.ts CHANGED
@@ -64,6 +64,10 @@ export declare function disableByok(): void;
64
64
  export declare function isOllamaRunning(): Promise<boolean>;
65
65
  /** List available Ollama models */
66
66
  export declare function listOllamaModels(): Promise<string[]>;
67
+ /** Fetch the Ollama server version string (e.g. "0.19.0"), or null if unreachable */
68
+ export declare function getOllamaVersion(): Promise<string | null>;
69
+ /** Detect whether Ollama is using the MLX backend (Ollama 0.19+ on Apple Silicon) */
70
+ export declare function isOllamaMLXBackend(): Promise<boolean>;
67
71
  /** Set up Ollama as the active provider */
68
72
  export declare function setupOllama(model?: string): Promise<boolean>;
69
73
  /** Check if LM Studio is running locally (default port 1234) */
package/dist/auth.js CHANGED
@@ -78,12 +78,12 @@ export const PROVIDERS = {
78
78
  name: 'DeepSeek',
79
79
  apiUrl: 'https://api.deepseek.com/chat/completions',
80
80
  apiStyle: 'openai',
81
- defaultModel: 'deepseek-v4',
81
+ defaultModel: 'deepseek-v4', // ~1T MoE (37B active), 1M context
82
82
  fastModel: 'deepseek-chat',
83
- inputCost: 0.27,
84
- outputCost: 1.10,
83
+ inputCost: 0.50, // V4 pricing (cache hit: $0.10)
84
+ outputCost: 2.0,
85
85
  authHeader: 'bearer',
86
- models: ['deepseek-v4', 'deepseek-chat', 'deepseek-reasoner'],
86
+ models: ['deepseek-v4', 'deepseek-v4-reasoner', 'deepseek-chat', 'deepseek-reasoner'],
87
87
  },
88
88
  groq: {
89
89
  name: 'Groq',
@@ -661,6 +661,45 @@ export async function listOllamaModels() {
661
661
  return [];
662
662
  }
663
663
  }
664
+ /** Fetch the Ollama server version string (e.g. "0.19.0"), or null if unreachable */
665
+ export async function getOllamaVersion() {
666
+ try {
667
+ const res = await fetch(`${OLLAMA_HOST}/api/version`, { signal: AbortSignal.timeout(2000) });
668
+ if (!res.ok)
669
+ return null;
670
+ const data = await res.json();
671
+ return data.version ?? null;
672
+ }
673
+ catch {
674
+ return null;
675
+ }
676
+ }
677
+ /** Detect whether Ollama is using the MLX backend (Ollama 0.19+ on Apple Silicon) */
678
+ export async function isOllamaMLXBackend() {
679
+ try {
680
+ // Ollama 0.19+ exposes backend info in /api/ps (running models list)
681
+ // If the response contains "mlx" in the backend or accelerator field, MLX is active.
682
+ // Fallback: check Ollama version >= 0.19 on Apple Silicon (MLX is the default).
683
+ const res = await fetch(`${OLLAMA_HOST}/api/ps`, { signal: AbortSignal.timeout(2000) });
684
+ if (res.ok) {
685
+ const text = await res.text();
686
+ if (/mlx/i.test(text))
687
+ return true;
688
+ }
689
+ // Heuristic: Ollama 0.19+ on arm64 darwin defaults to MLX
690
+ const version = await getOllamaVersion();
691
+ if (version && process.arch === 'arm64' && process.platform === 'darwin') {
692
+ const parts = version.split('.').map(Number);
693
+ const [major = 0, minor = 0] = parts;
694
+ if (major > 0 || minor >= 19)
695
+ return true;
696
+ }
697
+ return false;
698
+ }
699
+ catch {
700
+ return false;
701
+ }
702
+ }
664
703
  /** Set up Ollama as the active provider */
665
704
  export async function setupOllama(model) {
666
705
  const running = await isOllamaRunning();