snapkitty
hardware
erlang
SNAPKITTYWEST's picture
push from SNAPKITTYWEST/grisp-shadow-fleet
b8a03fa verified
Raw History Blame Contribute Delete
7.89 kB
import { DesktopCommandBroker } from './sandbox.js';
import { readWorkspaceTextFile } from './workspace.js';
import type { DesktopToolEvent, DesktopToolSummary } from '../shared.js';
export interface ToolCallRequest {
readonly name: string;
readonly input: Record<string, unknown>;
}
const LEDGER_API = 'https://sovereign-email-agent.snapkittywest.workers.dev/api/commit';
export const SOVEREIGN_TOOLS: ReadonlyArray<DesktopToolSummary> = [
{
name: 'workspace.search',
description: 'Search this repo with ripgrep. Read-only.',
inputSchema: { pattern: 'string', path: 'optional string path inside repo' },
},
{
name: 'workspace.read_file',
description: 'Read a text file from inside this repo. Read-only.',
inputSchema: { path: 'string path inside repo' },
},
{
name: 'repo.status',
description: 'Run git status -sb. Read-only.',
inputSchema: {},
},
{
name: 'repo.diff_stat',
description: 'Run git diff --stat. Read-only.',
inputSchema: {},
},
{
name: 'repo.test',
description: 'Run npm test through the governed command broker.',
inputSchema: {},
},
{
name: 'repo.build',
description: 'Run npm run build through the governed command broker.',
inputSchema: {},
},
{
name: 'repo.verify',
description: 'Run npm run verify through the governed command broker.',
inputSchema: {},
},
{
name: 'ledger.seal',
description: 'Seal the current file to the Fibonacci WORM ledger and commit to GitHub. Artifact hash (SHA-512 β†’ 0xFFFFFFFF β†’ SHA-512) recorded on-chain.',
inputSchema: { path: 'string β€” owner/repo/file/path.ext', code: 'string β€” file content', branch: 'optional string β€” default main' },
},
];
const TOOL_NAMES = new Set(SOVEREIGN_TOOLS.map(tool => tool.name));
export class SovereignToolBroker {
constructor(
private readonly repoRoot: string,
private readonly commands: DesktopCommandBroker,
) {}
list(): ReadonlyArray<DesktopToolSummary> {
return SOVEREIGN_TOOLS;
}
async execute(call: ToolCallRequest): Promise<DesktopToolEvent> {
try {
if (!TOOL_NAMES.has(call.name)) {
return { name: call.name, ok: false, output: `Unknown tool: ${call.name}` };
}
switch (call.name) {
case 'workspace.search':
return this.runCommandTool(call.name, buildSearchCommand(call.input));
case 'workspace.read_file': {
const path = requireString(call.input, 'path');
const file = await readWorkspaceTextFile(this.repoRoot, path);
return {
name: call.name,
ok: true,
output: truncateToolOutput(`FILE ${file.relativePath}\n\n${file.content}`),
};
}
case 'repo.status':
return this.runCommandTool(call.name, 'git status -sb');
case 'repo.diff_stat':
return this.runCommandTool(call.name, 'git diff --stat');
case 'repo.test':
return this.runCommandTool(call.name, 'npm test');
case 'repo.build':
return this.runCommandTool(call.name, 'npm run build');
case 'repo.verify':
return this.runCommandTool(call.name, 'npm run verify');
case 'ledger.seal':
return this.sealToLedger(call);
default:
return { name: call.name, ok: false, output: `Unhandled tool: ${call.name}` };
}
} catch (error) {
return {
name: call.name,
ok: false,
output: error instanceof Error ? error.message : String(error),
};
}
}
private async sealToLedger(call: ToolCallRequest): Promise<DesktopToolEvent> {
const path = requireString(call.input, 'path');
const code = requireString(call.input, 'code');
const branch = typeof call.input.branch === 'string' ? call.input.branch : 'main';
const res = await fetch(LEDGER_API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path, code, branch, author: 'bob-desktop-ide' }),
});
if (!res.ok) {
const err = await res.text();
return { name: call.name, ok: false, output: `Ledger API error ${res.status}: ${err}` };
}
const data = await res.json() as Record<string, unknown>;
const ledger = data.ledger as Record<string, unknown>;
const output = [
`SOVEREIGN COMMIT SEALED`,
`══════════════════════════════`,
` File: ${data.file}`,
` Branch: ${data.branch}`,
` Commit: ${data.commit}`,
` URL: ${data.url}`,
``,
`FIBONACCI LEDGER`,
` Block: ${ledger.block}`,
` F(${ledger.block}): ${ledger.fib}`,
` Hash: ${data.artifact_hash}`,
` Pipeline: ${data.pipeline}`,
`══════════════════════════════`,
].join('\n');
return { name: call.name, ok: true, output };
}
private async runCommandTool(name: string, command: string): Promise<DesktopToolEvent> {
const result = await this.commands.run({ command }, () => undefined);
const output = [
result.reason ? `reason: ${result.reason}` : '',
result.stdout,
result.stderr,
].filter(Boolean).join('\n');
return {
name,
ok: result.allowed && result.exitCode === 0,
output: truncateToolOutput(output || `exit=${result.exitCode}`),
};
}
}
export function parseToolCall(content: string): ToolCallRequest | undefined {
const candidates = [
...extractJsonCodeBlocks(content),
content.trim(),
];
for (const candidate of candidates) {
try {
const parsed = JSON.parse(candidate) as unknown;
const toolCall = typeof parsed === 'object' && parsed !== null && 'tool_call' in parsed
? (parsed as { tool_call?: unknown }).tool_call
: parsed;
if (typeof toolCall !== 'object' || toolCall === null) continue;
const record = toolCall as Record<string, unknown>;
if (typeof record.name !== 'string') continue;
const input = typeof record.input === 'object' && record.input !== null
? record.input as Record<string, unknown>
: {};
return { name: record.name, input };
} catch {
continue;
}
}
return undefined;
}
export function removeToolCallJson(content: string): string {
return content
.replace(/```(?:json)?\s*{[\s\S]*?"tool_call"[\s\S]*?}\s*```/gi, '')
.replace(/^\s*{[\s\S]*?"tool_call"[\s\S]*?}\s*$/i, '')
.trim();
}
export function formatToolResultForModel(call: ToolCallRequest, event: DesktopToolEvent): string {
return JSON.stringify({
tool_result: {
name: call.name,
ok: event.ok,
output: event.output,
},
}, null, 2);
}
function buildSearchCommand(input: Record<string, unknown>): string {
const pattern = requireString(input, 'pattern');
const path = typeof input.path === 'string' && input.path.trim() ? input.path.trim() : '.';
return `rg ${quoteArg(pattern)} ${quoteArg(path)}`;
}
function requireString(input: Record<string, unknown>, key: string): string {
const value = input[key];
if (typeof value !== 'string' || !value.trim()) {
throw new Error(`Tool input field "${key}" must be a non-empty string.`);
}
return value.trim();
}
function quoteArg(value: string): string {
return `"${value.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
}
function extractJsonCodeBlocks(content: string): string[] {
const matches = [...content.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi)];
return matches.map(match => match[1]?.trim() ?? '').filter(Boolean);
}
function truncateToolOutput(output: string): string {
const limit = 12_000;
if (output.length <= limit) return output;
return `${output.slice(0, limit)}\n\n[tool output truncated at ${limit} chars]`;
}