Bypass mode (owner opt-in, default off): storebound settings bypass on|off|status and a one-time question in init; MCP owner_step (owner-only commands, dry run first), owner-only api operations with --yes --confirm, console plans may press buttons.final; every action logged to .storebound/owner-actions.log; docs/bypass.md, agent rules, tests
This commit is contained in:
parent
2598c4953a
commit
9bd17c401d
21 changed files with 218 additions and 13 deletions
14
cli/mcp.ts
14
cli/mcp.ts
|
|
@ -25,7 +25,8 @@ import { tmpdir } from 'node:os';
|
|||
import { join } from 'node:path';
|
||||
import pkg from '../package.json' with { type: 'json' };
|
||||
import { runCommand, type Running } from './lib/runner';
|
||||
import { listTools, TOOLS, toArgv, validateInput } from './mcp-tools';
|
||||
import { listTools, OWNER_STEP, TOOLS, toArgv, validateInput } from './mcp-tools';
|
||||
import { bypassOn, logOwnerAction } from './lib/bypass';
|
||||
import * as rt from './lib/rt';
|
||||
|
||||
/** Current (modern, stateless) revision. */
|
||||
|
|
@ -102,18 +103,23 @@ export async function handle(m: Msg, run: (argv: string[], id: Id) => Promise<{
|
|||
case 'server/discover':
|
||||
return ok({ supportedVersions: SUPPORTED, capabilities: { tools: {} }, instructions: INSTRUCTIONS, ttlMs: TOOLS_TTL_MS, cacheScope: 'public' });
|
||||
case 'ping': return ok({});
|
||||
case 'tools/list': return ok({ tools: listTools(), ...(era === 'modern' ? { ttlMs: TOOLS_TTL_MS, cacheScope: 'public' } : {}) });
|
||||
case 'tools/list': return ok({ tools: listTools(bypassOn()), ...(era === 'modern' ? { ttlMs: TOOLS_TTL_MS, cacheScope: 'public' } : {}) });
|
||||
case 'tools/call': {
|
||||
const name = String(m.params?.name ?? '');
|
||||
const tool = TOOLS.find(t => t.name === name);
|
||||
const tool = TOOLS.find(t => t.name === name) ?? (name === OWNER_STEP.name && bypassOn() ? OWNER_STEP : undefined);
|
||||
if (!tool) return err(-32602, `Unknown tool: ${name}`);
|
||||
const input = (m.params?.arguments ?? {}) as Record<string, unknown>;
|
||||
const problems = validateInput(tool, input);
|
||||
if (problems.length) return ok({ content: [{ type: 'text', text: `Invalid arguments: ${problems.join('; ')}` }], isError: true });
|
||||
const { argv: raw, dryRun } = toArgv(tool, input);
|
||||
const argv = textToFile(raw);
|
||||
// owner_step runs the real command with --yes; its dry run is the command's own --dry
|
||||
const argv = tool === OWNER_STEP && !dryRun ? [...textToFile(raw), '--yes'] : textToFile(raw);
|
||||
const r = await run(argv, m.id);
|
||||
let out = clip(r.out, argv.includes('--json'));
|
||||
if (tool === OWNER_STEP) {
|
||||
const file = logOwnerAction(process.env.STOREBOUND_PROJECT || process.cwd(), { command: `storebound ${argv.join(' ')}`, dry: dryRun, exit: r.code, summary: r.out.trim().split('\n').slice(-1)[0] });
|
||||
out += `\n(bypass mode: logged to ${file})`;
|
||||
}
|
||||
if (dryRun) out += '\n\n(dry run: nothing was changed. Ask the owner before calling again with dry_run: false.)';
|
||||
const structured = { exitCode: r.code, dryRun, command: `storebound ${argv.join(' ')}`, output: out };
|
||||
return ok({ content: [{ type: 'text', text: `$ storebound ${argv.join(' ')}\n${out}\n[exit ${r.code}]` }], structuredContent: structured, isError: r.code !== 0 });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue