@voiden/runner 2.3.0-beta.1 → 2.3.0-beta.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"headlessContext.d.ts","sourceRoot":"","sources":["../src/headlessContext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,KAAK,EAAE,aAAa,EAA+C,MAAM,oBAAoB,CAAA;AAEpG,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,OAAe,GACvB,aAAa,CAmFf"}
1
+ {"version":3,"file":"headlessContext.d.ts","sourceRoot":"","sources":["../src/headlessContext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAUH,OAAO,KAAK,EAAE,aAAa,EAA+C,MAAM,oBAAoB,CAAA;AAEpG,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,OAAe,GACvB,aAAa,CAoHf"}
@@ -16,6 +16,10 @@
16
16
  import { hookRegistry, requestOrchestrator, executeWebSocket, executeGrpc } from '@voiden/executors';
17
17
  import { registerBlockSchema } from './blockSchemaRegistry.js';
18
18
  import { registerRequestContainer as registerContainerDef } from './requestContainerRegistry.js';
19
+ import { registerToolProvider as registerToolProviderFn } from './toolRegistry.js';
20
+ import { registerMcpToolCapabilityProvider as registerMcpToolCapabilityProviderFn } from './mcpToolCapability.js';
21
+ import { collectVoidFiles } from './discovery.js';
22
+ import { runVoidFile, getRequestPreview } from './runner.js';
19
23
  export function createHeadlessPluginContext(extensionId, verbose = false) {
20
24
  return {
21
25
  // ── Pipeline hook registration (for hook plugins: scripting, auth, etc.) ─
@@ -61,6 +65,20 @@ export function createHeadlessPluginContext(extensionId, verbose = false) {
61
65
  },
62
66
  // ── Verbosity ────────────────────────────────────────────────────────
63
67
  verbose,
68
+ // ── Runner primitives (for the plugin owning the /tool block) ─────────
69
+ // A plugin's runner bundle can't `import` from @voiden/runner itself —
70
+ // it's the host loading the plugin, not an externalized dependency, and
71
+ // the standalone-installed plugin copy has no node_modules to resolve it
72
+ // from anyway. So the handful of core-internal functions the /tool
73
+ // capability's relocated discover/verify/serve logic needs are handed
74
+ // over as already-bound closures here instead, the same way
75
+ // `protocols.executeWebSocket` already exposes a core-internal
76
+ // implementation to socket plugins without them importing it.
77
+ runnerPrimitives: {
78
+ collectVoidFiles: (inputPath) => collectVoidFiles(inputPath),
79
+ runVoidFile: (filePath, options) => runVoidFile(filePath, options),
80
+ getRequestPreview: (blocks) => getRequestPreview(blocks),
81
+ },
64
82
  // ── Reporting ────────────────────────────────────────────────────────
65
83
  report: {
66
84
  add: (_entry) => {
@@ -88,6 +106,22 @@ export function createHeadlessPluginContext(extensionId, verbose = false) {
88
106
  registerRequestContainer: (def) => {
89
107
  registerContainerDef(def);
90
108
  },
109
+ // Host capability not yet in the published @voiden/sdk RunnerContext
110
+ // type, same treatment as registerRequestContainer above — lets the
111
+ // plugin owning the /tool block declare its own block→tool-declaration
112
+ // extraction function, so voiden-runner's tool discovery/verification
113
+ // works without hardcoding knowledge of that block's shape.
114
+ registerToolProvider: (extractFn) => {
115
+ registerToolProviderFn(extractFn);
116
+ },
117
+ // Host capability not yet in the published @voiden/sdk RunnerContext
118
+ // type, same treatment as registerToolProvider above — lets the plugin
119
+ // owning the /tool block hand back its discover/validate/verify/serve
120
+ // implementation (mcpToolCapability.ts's McpToolCapabilityProvider),
121
+ // so voiden-runner core never hardcodes that block's own semantics.
122
+ registerMcpToolCapabilityProvider: (p) => {
123
+ registerMcpToolCapabilityProviderFn(p);
124
+ },
91
125
  },
92
126
  };
93
127
  }
@@ -1 +1 @@
1
- {"version":3,"file":"headlessContext.js","sourceRoot":"","sources":["../src/headlessContext.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"headlessContext.js","sourceRoot":"","sources":["../src/headlessContext.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -6,6 +6,12 @@ import { fileURLToPath } from 'url';
6
6
  import chalk from 'chalk';
7
7
  import { runVoidFile } from './runner.js';
8
8
  import { resolveFiles } from './discovery.js';
9
+ import { discoverTools, verifyTools, validateTools, upsertToolStatus, registerToolsFromDecisions, planServedTools, getCommitSha } from './mcpToolCapability.js';
10
+ import { registerFixedTools } from './mcpServing.js';
11
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
13
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
14
+ import { createServer as createHttpServer } from 'node:http';
9
15
  import { loadEnabledPlugins } from './plugins/loader.js';
10
16
  import { exportToCsv } from './report/csv.js';
11
17
  import { sendMailReport } from './report/mail.js';
@@ -1342,5 +1348,248 @@ mcpCmd
1342
1348
  console.log(` server registered: ${status.codex.serverRegistered ? chalk.green('yes') : chalk.gray('no')}`);
1343
1349
  console.log();
1344
1350
  });
1351
+ mcpCmd
1352
+ .command('serve [path]')
1353
+ .description('Serve this project as an MCP server — the same tools @voiden/mcp-server exposes ' +
1354
+ '(list/run/write plus declared /tool capabilities), over stdio (default) or HTTP.\n\n' +
1355
+ ' Examples:\n' +
1356
+ ' voiden-runner mcp serve # stdio, current directory\n' +
1357
+ ' voiden-runner mcp serve ./api # stdio, specific project\n' +
1358
+ ' voiden-runner mcp serve --http --port 3900 # HTTP on 127.0.0.1:3900\n' +
1359
+ ' voiden-runner mcp serve --check # dry run — print what would be served, no live server\n')
1360
+ .option('--http', 'Serve over streamable HTTP instead of stdio')
1361
+ .option('-p, --port <port>', 'HTTP port (only with --http)', '3000')
1362
+ .option('--host <host>', 'HTTP bind address (only with --http) — binding beyond 127.0.0.1 is a real exposure risk', '127.0.0.1')
1363
+ .option('-e, --env <path>', 'Path to .env file for variable substitution')
1364
+ .option('--check', 'Print what would be served and exit, without starting a live server')
1365
+ .action(async (path, opts) => {
1366
+ const projectRoot = resolve(path ?? '.');
1367
+ const env = Object.fromEntries(Object.entries(process.env).filter(([, v]) => v !== undefined));
1368
+ if (opts.env) {
1369
+ const envPath = resolve(opts.env);
1370
+ if (!existsSync(envPath)) {
1371
+ console.error(chalk.red(`Env file not found: ${envPath}`));
1372
+ process.exit(EXIT_USAGE_ERROR);
1373
+ }
1374
+ try {
1375
+ Object.assign(env, loadEnvFile(envPath));
1376
+ }
1377
+ catch (err) {
1378
+ console.error(chalk.red(` ✗ ${err.message}`));
1379
+ process.exit(EXIT_USAGE_ERROR);
1380
+ }
1381
+ }
1382
+ if (opts.check) {
1383
+ // Dry run — no live server. Same underlying decision function real
1384
+ // serving uses, so this can never disagree with what actually gets
1385
+ // registered.
1386
+ const activePlugins = await loadEnabledPlugins();
1387
+ const decisions = await planServedTools(projectRoot, env, activePlugins);
1388
+ console.log(`\n${decisions.length} /tool block(s) found in ${projectRoot}\n`);
1389
+ for (const d of decisions) {
1390
+ if (d.excluded) {
1391
+ console.log(` [EXCLUDED] ${d.tool.name}`);
1392
+ for (const reason of d.excludedReasons ?? [])
1393
+ console.log(` ${reason}`);
1394
+ continue;
1395
+ }
1396
+ const label = d.served ? (d.descriptionNote ? `SERVED (${d.status.state})` : 'SERVED') : 'WITHDRAWN';
1397
+ console.log(` [${label}] ${d.tool.name} — ${d.status.state}${d.status.note ? `: ${d.status.note}` : ''}`);
1398
+ }
1399
+ console.log(chalk.gray(` (plus the 4 fixed tools: list_void_files, list_requests, run_request, write_result)`));
1400
+ console.log();
1401
+ const anyFailing = decisions.some((d) => d.excluded || d.status?.state === 'failing');
1402
+ process.exit(anyFailing ? EXIT_RUN_FAILURE : EXIT_SUCCESS);
1403
+ }
1404
+ // Verification (real network calls) runs exactly once here, regardless
1405
+ // of transport — never repeated per HTTP request below.
1406
+ const activePlugins = await loadEnabledPlugins();
1407
+ const decisions = await planServedTools(projectRoot, env, activePlugins);
1408
+ const commitSha = getCommitSha(projectRoot);
1409
+ const servedCount = decisions.filter((d) => d.served).length;
1410
+ // Shared across calls so {{process.xxx}} runtime variables chain the
1411
+ // same way they do for the stdio path and for @voiden/mcp-server.
1412
+ const runtimeVars = {};
1413
+ if (opts.http) {
1414
+ const port = Number(opts.port);
1415
+ const host = opts.host;
1416
+ // A fresh McpServer + transport per HTTP request — this is how the
1417
+ // SDK's own stateless example (examples/server/simpleStatelessStreamableHttp.js)
1418
+ // does it, not an arbitrary choice: reusing one transport across
1419
+ // requests returns 500s. Cheap: registration is just schema/handler
1420
+ // wiring against the already-computed `decisions`, no re-verification.
1421
+ const httpServer = createHttpServer(async (req, res) => {
1422
+ try {
1423
+ const requestServer = new McpServer({ name: 'voiden-runner', version: '1.0.0' });
1424
+ registerFixedTools(requestServer, projectRoot, runtimeVars, activePlugins);
1425
+ registerToolsFromDecisions(requestServer, decisions, env, runtimeVars, activePlugins, commitSha);
1426
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
1427
+ await requestServer.connect(transport);
1428
+ res.on('close', () => {
1429
+ transport.close();
1430
+ requestServer.close();
1431
+ });
1432
+ await transport.handleRequest(req, res);
1433
+ }
1434
+ catch (err) {
1435
+ console.error(chalk.red(` ✗ Error handling MCP request: ${err?.message ?? String(err)}`));
1436
+ if (!res.headersSent) {
1437
+ res.writeHead(500, { 'content-type': 'application/json' }).end(JSON.stringify({
1438
+ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error' }, id: null,
1439
+ }));
1440
+ }
1441
+ }
1442
+ });
1443
+ httpServer.listen(port, host, () => {
1444
+ console.error(chalk.green(` ✓ voiden-runner mcp serve — listening on http://${host}:${port}/mcp`));
1445
+ console.error(chalk.gray(` ${servedCount} tool(s) served (plus list_void_files, list_requests, run_request, write_result)`));
1446
+ if (host !== '127.0.0.1' && host !== 'localhost') {
1447
+ console.error(chalk.red(` ⚠ Bound to ${host} — reachable beyond this machine. Make sure that's intended.`));
1448
+ }
1449
+ });
1450
+ // No process.exit() — stays alive until Ctrl-C, same as the stdio path below.
1451
+ }
1452
+ else {
1453
+ // stdio: one persistent server for the process lifetime — stdout is
1454
+ // reserved for the JSON-RPC stream, so nothing gets printed there.
1455
+ // Startup info goes to stderr only, same discipline @voiden/mcp-server's
1456
+ // own entrypoint already follows (it prints nothing).
1457
+ const server = new McpServer({ name: 'voiden-runner', version: '1.0.0' });
1458
+ registerFixedTools(server, projectRoot, runtimeVars, activePlugins);
1459
+ registerToolsFromDecisions(server, decisions, env, runtimeVars, activePlugins, commitSha);
1460
+ await server.connect(new StdioServerTransport());
1461
+ }
1462
+ });
1463
+ // ── voiden-runner tool ────────────────────────────────────────────────────────
1464
+ //
1465
+ // Discovery + verification for /tool blocks (voiden-mcp-tool plugin) — the
1466
+ // standalone CLI surface for the same discoverTools/verifyTools functions
1467
+ // @voiden/mcp-server will use for live agent-serving. No scheduling here:
1468
+ // `cadence` on a verify entry is a tag `--cadence` filters by, not something
1469
+ // this command enforces timing for — that's a human/CI decision, same as
1470
+ // deciding when to run `voiden-runner run` at all.
1471
+ const toolCmd = program
1472
+ .command('tool')
1473
+ .description('Discover and verify /tool blocks — capabilities declared for AI agents');
1474
+ toolCmd
1475
+ .command('list [paths...]')
1476
+ .description('List every /tool block found under the given path(s) (default: current directory) — does not execute anything')
1477
+ .option('--json', 'Output as JSON')
1478
+ .action(async (paths, opts) => {
1479
+ const targets = paths.length > 0 ? paths : ['.'];
1480
+ const allTools = [];
1481
+ const activePlugins = await loadEnabledPlugins();
1482
+ for (const p of targets) {
1483
+ allTools.push(...await discoverTools(resolve(p), { activePlugins }));
1484
+ }
1485
+ if (opts.json) {
1486
+ console.log(JSON.stringify(allTools, null, 2));
1487
+ return;
1488
+ }
1489
+ if (allTools.length === 0) {
1490
+ console.log(chalk.yellow(' No /tool blocks found.'));
1491
+ return;
1492
+ }
1493
+ console.log();
1494
+ for (const tool of allTools) {
1495
+ console.log(chalk.bold(` ${tool.name}`) + chalk.gray(` — ${relative(process.cwd(), tool.filePath)}${tool.sectionLabel ? ` [${tool.sectionLabel}]` : ''}`));
1496
+ if (tool.description)
1497
+ console.log(chalk.gray(` ${tool.description}`));
1498
+ console.log(chalk.gray(` params: ${tool.params.length} verifies: ${tool.verifies.length} on-failure: ${tool.onFailure}`));
1499
+ console.log();
1500
+ }
1501
+ });
1502
+ toolCmd
1503
+ .command('verify [paths...]')
1504
+ .description("Run each /tool block's verification requests and report verified / unverified / failing.\n\n" +
1505
+ ' Examples:\n' +
1506
+ ' voiden-runner tool verify\n' +
1507
+ ' voiden-runner tool verify ./api/ --cadence nightly\n' +
1508
+ ' voiden-runner tool verify --json --write\n')
1509
+ .option('--cadence <tag>', 'Only run verification requests tagged with this cadence — omit to run every entry regardless of tag')
1510
+ .option('--json', 'Output as JSON (suppresses normal output — useful for CI)')
1511
+ .option('--write', 'Write the computed status back into each /tool block. Off by default — verification always recomputes fresh and never trusts a stale write-back')
1512
+ .option('-e, --env <path>', 'Path to .env or .yaml file for variable substitution')
1513
+ .action(async (paths, opts) => {
1514
+ const targets = paths.length > 0 ? paths : ['.'];
1515
+ const env = Object.fromEntries(Object.entries(process.env).filter(([, v]) => v !== undefined));
1516
+ if (opts.env) {
1517
+ const envPath = resolve(opts.env);
1518
+ if (!existsSync(envPath)) {
1519
+ console.error(chalk.red(`Env file not found: ${envPath}`));
1520
+ process.exit(EXIT_USAGE_ERROR);
1521
+ }
1522
+ try {
1523
+ Object.assign(env, loadEnvFile(envPath));
1524
+ }
1525
+ catch (err) {
1526
+ console.error(chalk.red(` ✗ ${err.message}`));
1527
+ process.exit(EXIT_USAGE_ERROR);
1528
+ }
1529
+ }
1530
+ const activePlugins = await loadEnabledPlugins();
1531
+ const allTools = [];
1532
+ for (const p of targets) {
1533
+ allTools.push(...await discoverTools(resolve(p), { activePlugins }));
1534
+ }
1535
+ if (allTools.length === 0) {
1536
+ if (opts.json)
1537
+ console.log(JSON.stringify({ tools: [], issues: [] }, null, 2));
1538
+ else
1539
+ console.log(chalk.yellow(' No /tool blocks found.'));
1540
+ process.exit(EXIT_SUCCESS);
1541
+ }
1542
+ const { validTools, issues } = await validateTools(resolve(targets[0]), allTools);
1543
+ const statuses = await verifyTools(validTools, { cadence: opts.cadence, env, activePlugins });
1544
+ if (opts.write) {
1545
+ for (const status of statuses) {
1546
+ try {
1547
+ upsertToolStatus(status.tool.filePath, status.tool.toolBlockUid, { state: status.state, note: status.note });
1548
+ }
1549
+ catch (err) {
1550
+ console.error(chalk.red(` ✗ Failed to write status for "${status.tool.name}": ${err?.message ?? String(err)}`));
1551
+ }
1552
+ }
1553
+ }
1554
+ const anyFailing = statuses.some(s => s.state === 'failing') || issues.length > 0;
1555
+ if (opts.json) {
1556
+ console.log(JSON.stringify({ tools: statuses, issues }, null, 2));
1557
+ }
1558
+ else {
1559
+ console.log();
1560
+ // Structurally excluded tools (§1.6) — reported distinctly from a
1561
+ // verification failure: this is "the contract itself doesn't add up,"
1562
+ // not "the contract's proof failed." Neither ran nor counted below.
1563
+ const excludedNames = [...new Set(issues.map(i => i.tool.name))];
1564
+ for (const name of excludedNames) {
1565
+ console.log(` ${chalk.red('✗ excluded')} ${chalk.bold(name)}`);
1566
+ for (const issue of issues.filter(i => i.tool.name === name)) {
1567
+ console.log(chalk.gray(` [${issue.check}] ${issue.message}`));
1568
+ }
1569
+ console.log();
1570
+ }
1571
+ for (const status of statuses) {
1572
+ const icon = status.state === 'verified' ? chalk.green('✓ verified ') :
1573
+ status.state === 'failing' ? chalk.red('✗ failing ') :
1574
+ chalk.yellow('○ unverified');
1575
+ console.log(` ${icon} ${chalk.bold(status.tool.name)}` + chalk.gray(` — ${relative(process.cwd(), status.tool.filePath)}${status.tool.sectionLabel ? ` [${status.tool.sectionLabel}]` : ''}`));
1576
+ if (status.note)
1577
+ console.log(chalk.gray(` ${status.note}`));
1578
+ for (const r of status.results) {
1579
+ const rIcon = r.passed ? chalk.green('✓') : chalk.red('✗');
1580
+ console.log(` ${rIcon} ${r.entry.role}: ${r.entry.sectionLabel}` + (r.reason ? chalk.gray(` (${r.reason})`) : ''));
1581
+ }
1582
+ console.log();
1583
+ }
1584
+ const verified = statuses.filter(s => s.state === 'verified').length;
1585
+ const unverified = statuses.filter(s => s.state === 'unverified').length;
1586
+ const failing = statuses.filter(s => s.state === 'failing').length;
1587
+ console.log(chalk.bold(` ${statuses.length} tool(s)`) + chalk.gray(` — ${chalk.green(verified + ' verified')}, ${chalk.yellow(unverified + ' unverified')}, ${chalk.red(failing + ' failing')}`) + (excludedNames.length > 0 ? chalk.red(`, ${excludedNames.length} excluded`) : ''));
1588
+ if (opts.write)
1589
+ console.log(chalk.gray(' Status written back into each /tool block.'));
1590
+ console.log();
1591
+ }
1592
+ process.exit(anyFailing ? EXIT_RUN_FAILURE : EXIT_SUCCESS);
1593
+ });
1345
1594
  program.parse();
1346
1595
  //# sourceMappingURL=index.js.map