@kernel.chat/kbot 3.99.34 → 4.0.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p align="center">
4
4
  <strong>kbot</strong><br>
5
- Open-source terminal AI agent. 787+ tools. 35 agents. 20 providers. Dreams, learns, watches your system, controls your phone. $0 local.
5
+ Open-source terminal AI agent. 100+ specialist skills. 35 specialist agents. 20 providers. Dreams, learns, watches your system, controls your phone. $0 local.
6
6
  </p>
7
7
 
8
8
  <p align="center">
@@ -31,7 +31,8 @@ Most terminal AI agents lock you into one provider, one model, one way of workin
31
31
  - **Runs fully offline** — Embedded llama.cpp, Ollama, LM Studio, or Jan. $0, fully private.
32
32
  - **Learns your patterns** — Bayesian skill ratings + pattern extraction. Gets faster over time.
33
33
  - **35 specialist agents** — auto-routes your request to the right expert (coder, researcher, writer, guardian, quant, and 30 more). Run any agent manually: `kbot --agent <id> "<prompt>"`. List them: `kbot agents`.
34
- - **787+ tools** — files, bash, git, GitHub, web search, deploy, database, game dev, VFX, research, science, finance, security, music production, iPhone control, and more.
34
+ - **100+ specialist skills** — files, bash, git, GitHub, web search, deploy, database, game dev, VFX, research, science, finance, security, music production, iPhone control, and more.
35
+ - **v4.0 evidence-based curation** — went from 670 skills to ~100. Every kept skill has telemetry, agent reference, or test coverage. Everything else moved to plugins.
35
36
  - **Programmatic SDK** — use kbot as a library in your own apps.
36
37
  - **MCP server built in** — plug kbot into Claude Code, Cursor, VS Code, Zed, or Neovim as a tool provider.
37
38
 
@@ -165,8 +166,8 @@ Checks security, documentation, code quality, CI/CD, community health, and DevOp
165
166
  |---|---|---|---|---|---|
166
167
  | AI providers | 20 | 1 | 1 | 6 | 75+ |
167
168
  | Specialist agents | 35 | 0 | 0 | 0 | 0 |
168
- | Built-in tools | 787+ | ~20 | ~15 | ~10 | ~15 |
169
- | Science tools | 114 | 0 | 0 | 0 | 0 |
169
+ | Built-in skills | 100+ | ~20 | ~15 | ~10 | ~15 |
170
+ | Science skills | included | 0 | 0 | 0 | 0 |
170
171
  | Memory system | 7-tier bidirectional | File-based | No | No | No |
171
172
  | Dream engine | Yes ($0 local) | Cloud API | No | No | No |
172
173
  | Service watchdog | Yes | No | No | No | No |
@@ -228,7 +229,9 @@ kbot auto-routes to the right agent for each task. Or pick one with `--agent <na
228
229
  | **Domain** | infrastructure, quant, investigator, oracle, chronist, sage, communicator, adapter |
229
230
  | **Presets** | claude-code, cursor, copilot, creative, developer |
230
231
 
231
- ## 686+ Tools
232
+ ## 100+ Specialist Skills
233
+
234
+ As of v4.0, kbot ships ~100 curated skills (down from 670 — every kept skill has telemetry, agent reference, or test coverage). The rest are available as plugins.
232
235
 
233
236
  | Category | Examples |
234
237
  |----------|---------|
@@ -320,7 +323,7 @@ graph TD
320
323
  D -->|Multi-step| F[Autonomous Planner]
321
324
  E --> G[Provider API + Tool Loop]
322
325
  F --> G
323
- G --> H{686+ Tools}
326
+ G --> H{100+ Skills}
324
327
  H --> I[File ops, bash, git, GitHub, search, deploy, DB, game dev...]
325
328
  G --> J[Learning Engine]
326
329
  J --> K[Patterns + Solutions + User Profile]
@@ -391,6 +391,7 @@ export function getTrustReport() {
391
391
  export function registerAgentProtocolTools() {
392
392
  registerTool({
393
393
  name: 'agent_handoff',
394
+ deprecated: true,
394
395
  description: 'Create a handoff to transfer work to another agent. Includes context, artifacts, and priority. The receiving agent can accept or reject. Use this when a task is better suited for a different specialist.',
395
396
  parameters: {
396
397
  from: { type: 'string', description: 'Agent ID initiating the handoff', required: true },
@@ -430,6 +431,7 @@ export function registerAgentProtocolTools() {
430
431
  });
431
432
  registerTool({
432
433
  name: 'blackboard_write',
434
+ deprecated: true,
433
435
  description: 'Write to the shared agent blackboard (working memory). Any agent can write facts, hypotheses, decisions, artifacts, or questions. Other agents can read these to coordinate without direct communication.',
434
436
  parameters: {
435
437
  key: { type: 'string', description: 'Key to write (e.g., "architecture_decision", "security_finding")', required: true },
@@ -464,6 +466,7 @@ export function registerAgentProtocolTools() {
464
466
  });
465
467
  registerTool({
466
468
  name: 'blackboard_read',
469
+ deprecated: true,
467
470
  description: 'Read from the shared agent blackboard. Query a specific key or list all entries filtered by type. Use this to see what other agents have written and coordinate work.',
468
471
  parameters: {
469
472
  key: { type: 'string', description: 'Specific key to read. If omitted, returns all entries.' },
@@ -504,6 +507,7 @@ export function registerAgentProtocolTools() {
504
507
  });
505
508
  registerTool({
506
509
  name: 'agent_propose',
510
+ deprecated: true,
507
511
  description: 'Propose an approach for multi-agent negotiation. Other agents can vote agree/disagree/abstain. Use resolve to determine the outcome. Ties are broken by trust scores.',
508
512
  parameters: {
509
513
  action: { type: 'string', description: 'Action: propose, vote, resolve, or status', required: true },
@@ -589,6 +593,7 @@ export function registerAgentProtocolTools() {
589
593
  });
590
594
  registerTool({
591
595
  name: 'agent_trust',
596
+ deprecated: true,
592
597
  description: 'Check or update trust scores for agents. Trust is asymmetric: success adds 0.05, failure subtracts 0.10. Scores persist across sessions in ~/.kbot/trust.json.',
593
598
  parameters: {
594
599
  action: { type: 'string', description: 'Action: check, update, best, or report', required: true },
package/dist/auth.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  declare const KBOT_DIR: string;
2
2
  declare const CONFIG_PATH: string;
3
- export type ByokProvider = 'anthropic' | 'openai' | 'google' | 'mistral' | 'xai' | 'deepseek' | 'groq' | 'together' | 'fireworks' | 'perplexity' | 'cohere' | 'nvidia' | 'sambanova' | 'cerebras' | 'openrouter' | 'lmstudio' | 'jan' | 'ollama' | 'kbot-local' | 'embedded';
3
+ export type ByokProvider = 'anthropic' | 'openai' | 'google' | 'mistral' | 'xai' | 'deepseek' | 'groq' | 'together' | 'fireworks' | 'perplexity' | 'cohere' | 'nvidia' | 'sambanova' | 'cerebras' | 'openrouter' | 'lmstudio' | 'jan' | 'ollama' | 'kbot-local' | 'llada' | 'embedded';
4
4
  export interface ProviderConfig {
5
5
  name: string;
6
6
  apiUrl: string;
package/dist/auth.js CHANGED
@@ -18,6 +18,7 @@ const OLLAMA_HOST = process.env.OLLAMA_HOST || 'http://localhost:11434';
18
18
  const LMSTUDIO_HOST = process.env.LMSTUDIO_HOST || 'http://localhost:1234';
19
19
  const JAN_HOST = process.env.JAN_HOST || 'http://localhost:1337';
20
20
  const KBOT_LOCAL_HOST = process.env.KBOT_LOCAL_HOST || 'http://127.0.0.1:18789';
21
+ const LLADA_HOST = process.env.KBOT_LLADA_URL || 'http://localhost:8000';
21
22
  export const PROVIDERS = {
22
23
  anthropic: {
23
24
  name: 'Anthropic (Claude)',
@@ -241,6 +242,23 @@ export const PROVIDERS = {
241
242
  outputCost: 0,
242
243
  authHeader: 'bearer',
243
244
  },
245
+ llada: {
246
+ // LLaDA2.0-Uni — Inclusion AI unified discrete-diffusion multimodal LLM.
247
+ // Local, $0 path to image generation + multimodal understanding.
248
+ // SPEC: refine when LLaDA's API stabilizes — currently assumes an
249
+ // OpenAI-compatible server at $KBOT_LLADA_URL (default http://localhost:8000).
250
+ // The upstream repo (github.com/inclusionAI/LLaDA2.0-Uni) ships Python
251
+ // inference scripts today; SGLang serving is on their TODO list.
252
+ name: 'LLaDA2.0-Uni (Local)',
253
+ apiUrl: `${LLADA_HOST}/v1/chat/completions`,
254
+ apiStyle: 'openai',
255
+ defaultModel: 'llada2.0-uni',
256
+ fastModel: 'llada2.0-uni',
257
+ inputCost: 0,
258
+ outputCost: 0,
259
+ authHeader: 'bearer', // Auth is ignored when no key is set; local servers usually don't require one.
260
+ models: ['llada2.0-uni'],
261
+ },
244
262
  embedded: {
245
263
  name: 'Embedded (llama.cpp)',
246
264
  apiUrl: 'embedded://local', // Not a real URL — inference runs in-process
@@ -381,11 +399,11 @@ const ENV_KEYS = [
381
399
  ];
382
400
  /** Check if a provider is local (runs on this machine, may still need a token) */
383
401
  export function isLocalProvider(provider) {
384
- return provider === 'ollama' || provider === 'kbot-local' || provider === 'lmstudio' || provider === 'jan' || provider === 'embedded';
402
+ return provider === 'ollama' || provider === 'kbot-local' || provider === 'lmstudio' || provider === 'jan' || provider === 'embedded' || provider === 'llada';
385
403
  }
386
404
  /** Check if a provider needs no API key at all */
387
405
  export function isKeylessProvider(provider) {
388
- return provider === 'ollama' || provider === 'lmstudio' || provider === 'jan' || provider === 'embedded';
406
+ return provider === 'ollama' || provider === 'lmstudio' || provider === 'jan' || provider === 'embedded' || provider === 'llada';
389
407
  }
390
408
  /** Check if BYOK mode is enabled (via env var or config) */
391
409
  export function isByokEnabled() {
package/dist/cli.js CHANGED
@@ -102,7 +102,7 @@ async function main() {
102
102
  console.log(` ${chalk.cyan('https://github.com/isaacsight/kernel/issues')} ${chalk.dim('Bug reports')}`);
103
103
  console.log(` ${chalk.cyan('support@kernel.chat')} ${chalk.dim('Email (AI-assisted replies)')}`);
104
104
  console.log();
105
- console.log(` ${chalk.dim('35 specialist agents · 787+ tools · 20 providers · MIT licensed')}`);
105
+ console.log(` ${chalk.dim('35 specialist agents · 100+ skills · 20 providers · MIT licensed')}`);
106
106
  console.log();
107
107
  process.exit(0);
108
108
  });
@@ -582,10 +582,10 @@ async function main() {
582
582
  // ── Discovery Agent ──
583
583
  const discoveryCmd = program
584
584
  .command('discovery')
585
- .description('Autonomous outreach agent — finds conversations, drafts responses, posts for you');
585
+ .description('Background outreach agent — finds conversations, drafts responses, posts for you');
586
586
  discoveryCmd
587
587
  .command('start')
588
- .description('Start the discovery loop — scans HN, GitHub, Reddit and posts autonomously')
588
+ .description('Start the discovery loop — scans HN, GitHub, Reddit and posts in the background')
589
589
  .option('--dry-run', 'Find and draft but don\'t post')
590
590
  .option('--interval <minutes>', 'Poll interval in minutes', '60')
591
591
  .option('--model <model>', 'Ollama model for analysis', 'qwen2.5-coder:32b')
@@ -4202,7 +4202,7 @@ async function main() {
4202
4202
  const models = await listOllamaModels();
4203
4203
  if (models.length > 0)
4204
4204
  printInfo(`${models.length} models available. Using: ${ollamaModel || PROVIDERS.ollama.defaultModel}`);
4205
- printInfo('670+ tools ready. Type your prompt or press Enter for interactive mode.');
4205
+ printInfo('100+ skills ready. Type your prompt or press Enter for interactive mode.');
4206
4206
  }
4207
4207
  else {
4208
4208
  printError(`Cannot reach Ollama at ${ollamaHost}. Is it running?`);
@@ -4696,7 +4696,7 @@ async function byokFlow() {
4696
4696
  console.log();
4697
4697
  printSuccess(`BYOK mode enabled — ${providerConfig.name}`);
4698
4698
  printInfo('You pay the provider directly. No message limits. No restrictions.');
4699
- printInfo('All 362 tools + 35 agents + learning system = yours.');
4699
+ printInfo('All 100+ skills + 35 specialist agents + learning system = yours.');
4700
4700
  console.log();
4701
4701
  printSuccess('Ready. Run `kbot` to start.');
4702
4702
  }
@@ -4996,7 +4996,7 @@ async function startRepl(agentOpts, context, tier, byokActive = false, localActi
4996
4996
  const suggestions = await detectProjectSuggestions();
4997
4997
  console.log();
4998
4998
  console.log(chalk.dim(' ┌─────────────────────────────────────────────────┐'));
4999
- console.log(chalk.dim(' │') + chalk.bold(' 35 agents. 362 tools. Just say what you need. ') + chalk.dim(' │'));
4999
+ console.log(chalk.dim(' │') + chalk.bold(' 35 agents. 100+ skills. Just say what you need. ') + chalk.dim(' │'));
5000
5000
  console.log(chalk.dim(' │ │'));
5001
5001
  if (suggestions.length > 0) {
5002
5002
  for (const s of suggestions.slice(0, 4)) {
@@ -35,6 +35,17 @@ export interface PluginConfig {
35
35
  enabled: string[];
36
36
  disabled: string[];
37
37
  }
38
+ export interface LoadPluginsOptions {
39
+ /** Override the plugin directory (used by tests). Defaults to ~/.kbot/plugins. */
40
+ pluginsDir?: string;
41
+ /** Override the integrity manifest path. Defaults to ~/.kbot/plugins.json. */
42
+ manifestPath?: string;
43
+ /**
44
+ * Override the integrity-disabled flag. Defaults to reading
45
+ * `process.env.KBOT_PLUGIN_INTEGRITY === 'off'`.
46
+ */
47
+ integrityDisabled?: boolean;
48
+ }
38
49
  export interface SDKPluginManifest {
39
50
  name: string;
40
51
  version: string;
@@ -53,8 +64,18 @@ export interface SDKPluginManifest {
53
64
  /**
54
65
  * Load all installed and enabled SDK plugins.
55
66
  * Called at startup after the legacy plugins.ts loadPlugins() runs.
56
- */
57
- export declare function loadPlugins(verbose?: boolean): Promise<SDKPluginManifest[]>;
67
+ *
68
+ * Integrity contract (mirrors plugins.ts):
69
+ * - Reads the same manifest at `~/.kbot/plugins.json` (override via
70
+ * `KBOT_PLUGIN_MANIFEST` or `opts.manifestPath`).
71
+ * - If the manifest exists, only plugins whose `name` appears in
72
+ * `result.verified` are imported. Drift → throws `IntegrityError`.
73
+ * - If the manifest is missing, falls back to back-compat behaviour: all
74
+ * discovered, enabled plugins are imported (with a yellow info note).
75
+ * - `KBOT_PLUGIN_INTEGRITY=off` skips verification entirely with a loud
76
+ * yellow warning.
77
+ */
78
+ export declare function loadPlugins(verbose?: boolean, opts?: LoadPluginsOptions): Promise<SDKPluginManifest[]>;
58
79
  /**
59
80
  * Scaffold a new plugin with a full project structure.
60
81
  * Creates ~/.kbot/plugins/<name>/ with index.ts and package.json.
@@ -21,11 +21,32 @@ import { join, basename, resolve } from 'node:path';
21
21
  import { homedir } from 'node:os';
22
22
  import { pathToFileURL } from 'node:url';
23
23
  import { execSync } from 'node:child_process';
24
+ import chalk from 'chalk';
24
25
  import { registerTool } from './tools/index.js';
26
+ import { IntegrityError, verifyAllPlugins, enforce, } from './plugins-integrity.js';
25
27
  // ── Constants ────────────────────────────────────────────────────────────
26
28
  const KBOT_DIR = join(homedir(), '.kbot');
27
29
  const PLUGINS_DIR = join(KBOT_DIR, 'plugins');
30
+ // NOTE: PLUGINS_CONFIG (per-plugin enable/disable list) shares the path
31
+ // `~/.kbot/plugins.json` with the integrity manifest used by plugins.ts. This
32
+ // is a pre-existing collision in the SDK loader — the enable/disable JSON has
33
+ // shape `{ enabled, disabled }`, while the integrity manifest has shape
34
+ // `{ schemaVersion, plugins }`. In practice users running with the integrity
35
+ // manifest must override the SDK config path, or the integrity manifest path,
36
+ // via `KBOT_PLUGIN_MANIFEST`. The fix is out of scope for the integrity
37
+ // wiring; flagged here so we do not mask the collision in tests.
28
38
  const PLUGINS_CONFIG = join(KBOT_DIR, 'plugins.json');
39
+ /**
40
+ * Default integrity manifest path. Mirrors plugins.ts so a single manifest
41
+ * covers both drop-in `.js` plugins (handled by plugins.ts) and SDK-style
42
+ * directory plugins (handled here). The integrity manifest's `path` field is
43
+ * resolved relative to `~/.kbot/plugins/`, so:
44
+ * - `hello.js` → simple drop-in plugin
45
+ * - `my-tool/index.js` → SDK-style packaged plugin
46
+ * are both expressible in one manifest. Override via `KBOT_PLUGIN_MANIFEST`
47
+ * or the `manifestPath` option to `loadPlugins`.
48
+ */
49
+ const DEFAULT_MANIFEST_PATH = join(homedir(), '.kbot', 'plugins.json');
29
50
  // ── State ────────────────────────────────────────────────────────────────
30
51
  const loadedSDKPlugins = new Map();
31
52
  const registeredCommands = new Map();
@@ -219,19 +240,22 @@ function registerPluginHooks(plugin) {
219
240
  /**
220
241
  * Discover local plugins in ~/.kbot/plugins/<name>/ directories.
221
242
  * Each directory should contain an index.ts or index.js file.
243
+ *
244
+ * Pass `pluginsDir` to scan a custom location (used by tests). Defaults to
245
+ * the module-level `PLUGINS_DIR` (`~/.kbot/plugins`).
222
246
  */
223
- function discoverLocalPlugins() {
224
- ensureDir(PLUGINS_DIR);
247
+ function discoverLocalPlugins(pluginsDir = PLUGINS_DIR) {
248
+ ensureDir(pluginsDir);
225
249
  const results = [];
226
250
  let entries;
227
251
  try {
228
- entries = readdirSync(PLUGINS_DIR);
252
+ entries = readdirSync(pluginsDir);
229
253
  }
230
254
  catch {
231
255
  return results;
232
256
  }
233
257
  for (const entry of entries) {
234
- const dirPath = join(PLUGINS_DIR, entry);
258
+ const dirPath = join(pluginsDir, entry);
235
259
  try {
236
260
  const stat = statSync(dirPath);
237
261
  if (!stat.isDirectory())
@@ -328,16 +352,91 @@ function discoverNpmPlugins() {
328
352
  }
329
353
  return results;
330
354
  }
355
+ // ── Integrity Gate ───────────────────────────────────────────────────────
356
+ /**
357
+ * Run the integrity manifest gate before loading any SDK plugin module.
358
+ *
359
+ * Mirrors `plugins.ts`'s `runIntegrityGate` so the two loaders enforce the
360
+ * same fail-closed contract against the same manifest at `~/.kbot/plugins.json`.
361
+ *
362
+ * Behaviour:
363
+ * - If KBOT_PLUGIN_INTEGRITY=off (or `integrityDisabled === true`):
364
+ * emit a yellow warning and return `null` (verification skipped, all
365
+ * discovered plugins are eligible to load).
366
+ * - If the manifest file does not exist: emit a yellow info note and
367
+ * return `null` (back-compat — manifest is optional today).
368
+ * - If the manifest exists and verifies: return the `VerifyAllResult` so
369
+ * the caller can restrict loads to `result.verified`.
370
+ * - If the manifest exists and any plugin fails: throw `IntegrityError`
371
+ * (loader refuses to import any SDK plugin this session).
372
+ */
373
+ async function runIntegrityGate(manifestPath, pluginsDir, integrityDisabled) {
374
+ if (integrityDisabled) {
375
+ console.error(chalk.yellow(` ⚠ KBOT_PLUGIN_INTEGRITY=off — skipping integrity check for SDK plugins (NOT FOR PRODUCTION). ` +
376
+ `Plugins under ${pluginsDir} will load without hash checking.`));
377
+ return null;
378
+ }
379
+ if (!existsSync(manifestPath)) {
380
+ console.error(chalk.yellow(` ⚠ no plugin manifest at ${manifestPath} — plugin SDK loaded without integrity verification. ` +
381
+ `Create one to pin plugins by SHA-256 (see PLUGINS_INTEGRITY.md).`));
382
+ return null;
383
+ }
384
+ try {
385
+ const result = await verifyAllPlugins(manifestPath, pluginsDir);
386
+ if (result.failed.length > 0) {
387
+ const lines = result.failed
388
+ .map((f) => ` - ${f.name}: ${f.reason}`)
389
+ .join('\n');
390
+ console.error(chalk.red(` ✗ Plugin integrity check failed for ${result.failed.length} SDK plugin(s):\n${lines}\n` +
391
+ ` Manifest: ${manifestPath}\n` +
392
+ ` To refresh hashes, recompute SHA-256 for each plugin entry and update the manifest. ` +
393
+ `Set KBOT_PLUGIN_INTEGRITY=off ONLY for local dev; never in production.`));
394
+ enforce(result); // throws IntegrityError
395
+ }
396
+ return result;
397
+ }
398
+ catch (err) {
399
+ if (err instanceof IntegrityError)
400
+ throw err;
401
+ // loadManifest threw (malformed JSON, schema violation, etc.) — fail closed.
402
+ console.error(chalk.red(` ✗ Failed to load plugin integrity manifest at ${manifestPath}: ${err.message}`));
403
+ throw err;
404
+ }
405
+ }
331
406
  // ── Public API ───────────────────────────────────────────────────────────
332
407
  /**
333
408
  * Load all installed and enabled SDK plugins.
334
409
  * Called at startup after the legacy plugins.ts loadPlugins() runs.
410
+ *
411
+ * Integrity contract (mirrors plugins.ts):
412
+ * - Reads the same manifest at `~/.kbot/plugins.json` (override via
413
+ * `KBOT_PLUGIN_MANIFEST` or `opts.manifestPath`).
414
+ * - If the manifest exists, only plugins whose `name` appears in
415
+ * `result.verified` are imported. Drift → throws `IntegrityError`.
416
+ * - If the manifest is missing, falls back to back-compat behaviour: all
417
+ * discovered, enabled plugins are imported (with a yellow info note).
418
+ * - `KBOT_PLUGIN_INTEGRITY=off` skips verification entirely with a loud
419
+ * yellow warning.
335
420
  */
336
- export async function loadPlugins(verbose = false) {
421
+ export async function loadPlugins(verbose = false, opts = {}) {
422
+ const pluginsDir = opts.pluginsDir ?? PLUGINS_DIR;
423
+ const manifestPath = opts.manifestPath ?? process.env.KBOT_PLUGIN_MANIFEST ?? DEFAULT_MANIFEST_PATH;
424
+ const integrityDisabled = opts.integrityDisabled ?? process.env.KBOT_PLUGIN_INTEGRITY === 'off';
337
425
  const manifests = [];
338
- // Discover from both sources
339
- const localPlugins = discoverLocalPlugins();
340
- const npmPlugins = discoverNpmPlugins();
426
+ // Integrity gate runs BEFORE any SDK plugin file is imported. Throws
427
+ // IntegrityError on drift unless KBOT_PLUGIN_INTEGRITY=off.
428
+ const integrity = await runIntegrityGate(manifestPath, pluginsDir, integrityDisabled);
429
+ // When a manifest verified successfully, restrict loads to verified names.
430
+ // When the manifest is missing or integrity is disabled, allow every file.
431
+ const verifiedNames = integrity
432
+ ? new Set(integrity.verified)
433
+ : null;
434
+ // Discover from both sources. NPM plugins live in global node_modules and
435
+ // are not (yet) covered by the integrity manifest's relative-path scheme;
436
+ // when integrity is enforced, only local plugins listed in the manifest
437
+ // load. NPM plugins are skipped entirely under enforcement.
438
+ const localPlugins = discoverLocalPlugins(pluginsDir);
439
+ const npmPlugins = verifiedNames ? [] : discoverNpmPlugins();
341
440
  const allDiscovered = [
342
441
  ...localPlugins.map(p => ({ ...p, source: 'local' })),
343
442
  ...npmPlugins.map(p => ({ ...p, source: 'npm' })),
@@ -361,6 +460,17 @@ export async function loadPlugins(verbose = false) {
361
460
  manifests.push(manifest);
362
461
  continue;
363
462
  }
463
+ // When manifest verification ran, only load plugins whose name appears in
464
+ // the verified set. Discovered plugins not declared in the manifest are
465
+ // skipped (they could not have passed verification).
466
+ if (verifiedNames && !verifiedNames.has(name)) {
467
+ if (verbose) {
468
+ console.error(chalk.yellow(` ⚠ [SDK] Skipping ${name} — not declared in plugin manifest`));
469
+ }
470
+ manifest.error = 'not declared in plugin integrity manifest';
471
+ manifests.push(manifest);
472
+ continue;
473
+ }
364
474
  try {
365
475
  const plugin = await importPlugin(entryPath);
366
476
  manifest.version = plugin.version;
package/dist/plugins.js CHANGED
@@ -107,7 +107,21 @@ export async function loadPlugins(verbose = false, opts = {}) {
107
107
  const verifiedNames = integrity
108
108
  ? new Set(integrity.verified)
109
109
  : null;
110
- const files = readdirSync(pluginsDir).filter(f => PLUGIN_EXTENSIONS.some(ext => f.endsWith(ext)));
110
+ // Top-level plugin files
111
+ const topLevel = readdirSync(pluginsDir).filter(f => PLUGIN_EXTENSIONS.some(ext => f.endsWith(ext)));
112
+ // Forged subdirectory (created by forge.ts at runtime). v3.99.31 and earlier
113
+ // persisted forged tools here without the loader scanning for them; fixed in v4.0.
114
+ let forgedFiles = [];
115
+ try {
116
+ const forgedDir = join(pluginsDir, 'forged');
117
+ forgedFiles = readdirSync(forgedDir)
118
+ .filter(f => PLUGIN_EXTENSIONS.some(ext => f.endsWith(ext)))
119
+ .map(f => `forged/${f}`);
120
+ }
121
+ catch {
122
+ // forged/ doesn't exist — nothing to load
123
+ }
124
+ const files = [...topLevel, ...forgedFiles];
111
125
  if (files.length === 0)
112
126
  return [];
113
127
  for (const file of files) {
@@ -0,0 +1,98 @@
1
+ export interface LLaDAClientOptions {
2
+ baseUrl?: string;
3
+ apiKey?: string;
4
+ /** Default request timeout in ms. */
5
+ timeoutMs?: number;
6
+ /** Optional fetch override (used by tests). */
7
+ fetchImpl?: typeof fetch;
8
+ }
9
+ export interface LLaDAChatMessage {
10
+ role: 'system' | 'user' | 'assistant';
11
+ content: string | Array<{
12
+ type: 'text';
13
+ text: string;
14
+ } | {
15
+ type: 'image_url';
16
+ image_url: {
17
+ url: string;
18
+ };
19
+ }>;
20
+ }
21
+ export interface LLaDAChatRequest {
22
+ messages: LLaDAChatMessage[];
23
+ model?: string;
24
+ temperature?: number;
25
+ maxTokens?: number;
26
+ /** Optional thinking budget — LLaDA supports `mode: "thinking"` with `thinking_steps`. */
27
+ thinkingSteps?: number;
28
+ }
29
+ export interface LLaDAChatResponse {
30
+ text: string;
31
+ /** When `thinkingSteps` is set, LLaDA returns the reasoning trace. */
32
+ thinking?: string;
33
+ raw?: unknown;
34
+ }
35
+ export interface LLaDAImageRequest {
36
+ prompt: string;
37
+ /** Either "1024x1024" / "WxH" — converted to image_h/image_w server-side. */
38
+ size?: string;
39
+ /** Optional reference image (URL or base64 data URL) for image editing. */
40
+ refImage?: string;
41
+ /** Diffusion sampling steps (LLaDA defaults to 8 with the turbo decoder). */
42
+ steps?: number;
43
+ cfgScale?: number;
44
+ /** Enable LLaDA's interleaved thinking-then-generate mode. */
45
+ thinking?: boolean;
46
+ }
47
+ export interface LLaDAImageResponse {
48
+ /** A URL or `data:image/png;base64,...` payload. */
49
+ url: string;
50
+ /** Reasoning trace, only present when `thinking: true`. */
51
+ thinking?: string;
52
+ raw?: unknown;
53
+ }
54
+ export interface LLaDAUnderstandRequest {
55
+ prompt: string;
56
+ /** Pass exactly one of imageUrl or imageData (base64). */
57
+ imageUrl?: string;
58
+ imageData?: string;
59
+ model?: string;
60
+ maxTokens?: number;
61
+ }
62
+ export interface LLaDAUnderstandResponse {
63
+ text: string;
64
+ raw?: unknown;
65
+ }
66
+ /** Typed client for a LLaDA2.0-Uni HTTP server (OpenAI-compatible shape). */
67
+ export declare class LLaDAClient {
68
+ readonly baseUrl: string;
69
+ readonly apiKey: string | undefined;
70
+ private readonly timeoutMs;
71
+ private readonly fetchImpl;
72
+ constructor(opts?: LLaDAClientOptions);
73
+ /** Build standard headers — Authorization only attached when an apiKey is set. */
74
+ private headers;
75
+ private post;
76
+ /** Quick health probe. Resolves true when the server responds with 2xx. */
77
+ isReachable(): Promise<boolean>;
78
+ /**
79
+ * Text chat — OpenAI-compatible POST /v1/chat/completions.
80
+ * SPEC: refine when LLaDA's API stabilizes — currently assumes OpenAI-compatible shape.
81
+ */
82
+ chat(req: LLaDAChatRequest): Promise<LLaDAChatResponse>;
83
+ /**
84
+ * Image generation. The native LLaDA call is `model.generate_image(...)`;
85
+ * we expose it via a POST /v1/images/generations shim that accepts the
86
+ * extra LLaDA-specific knobs (`steps`, `cfg_scale`, `thinking`).
87
+ * SPEC: refine when LLaDA's API stabilizes — assumes OpenAI-compatible shape.
88
+ */
89
+ generateImage(req: LLaDAImageRequest): Promise<LLaDAImageResponse>;
90
+ /**
91
+ * Multimodal understanding: chat with an image attached.
92
+ * SPEC: refine when LLaDA's API stabilizes — uses OpenAI-vision content blocks.
93
+ */
94
+ understand(req: LLaDAUnderstandRequest): Promise<LLaDAUnderstandResponse>;
95
+ }
96
+ /** Convenience factory mirroring the rest of kbot's local-provider style. */
97
+ export declare function createLLaDAClient(opts?: LLaDAClientOptions): LLaDAClient;
98
+ //# sourceMappingURL=llada.d.ts.map