ai-chatbot/scripts/zcode-agent/audit-project-tools.mjs

45 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import ts from 'typescript';
import { readFile, writeFile, access } from 'node:fs/promises';
import { execFileSync } from 'node:child_process';
const catalog = JSON.parse(execFileSync(process.execPath, ['node_modules/tsx/dist/cli.mjs','-e', "import {PROJECT_AGENT_OPERATIONS} from './lib/ai/canvas-agent-runtime/project-operation-catalog';console.log(JSON.stringify(PROJECT_AGENT_OPERATIONS))"], { encoding: 'utf8' }));
const contracts = {}; const mismatch = [];
for (const operation of catalog) {
let file;
for (const root of ['app/api','app/(chat)/api']) {
const path = `${root}/${operation.path.slice('/api/'.length)}/route.ts`;
try { await access(path); file = path; break; } catch {}
}
if (!file) { if (operation.id !== 'smart-edit.get') mismatch.push(`${operation.id}: missing route`); continue; }
const source = await readFile(file,'utf8');
const methods = [...source.matchAll(/export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)/g)].map(match => match[1]);
if (!methods.includes(operation.method)) mismatch.push(`${operation.id}: method not implemented`);
const ast = ts.createSourceFile(file,source,ts.ScriptTarget.Latest,true); const schemas = {}, interfaces = {};
function walk(node) {
if (ts.isVariableDeclaration(node) && node.initializer && /schema$/i.test(node.name.getText(ast)) && /^z\./.test(node.initializer.getText(ast)))
schemas[node.name.getText(ast)] = node.initializer.getText(ast).slice(0,6000);
if (ts.isInterfaceDeclaration(node) && /request|payload|body/i.test(node.name.getText(ast))) interfaces[node.name.getText(ast)] = node.getText(ast).slice(0,6000);
ts.forEachChild(node,walk);
}
walk(ast);
contracts[operation.id] = { source: file, methods, schemas, interfaces,
queryKeys: [...new Set([...source.matchAll(/(?:searchParams|params)\.get\(["']([^"']+)["']\)/g)].map(match=>match[1]))] };
}
if (mismatch.length) throw new Error(mismatch.join('\n'));
await writeFile('lib/ai/canvas-agent-runtime/project-operation-contracts.json',JSON.stringify(contracts,null,2)+'\n');
console.log(JSON.stringify({ operations: catalog.length, routeContracts: Object.keys(contracts).length, deploymentOnly: 'smart-edit.get', unsupportedMethods: mismatch.length }));
const canvas = JSON.parse(execFileSync(process.execPath, ['node_modules/tsx/dist/cli.mjs','-e', "import {getCanvasAgentAccessManifest} from './lib/ai/canvas-agent-runtime/capability-access';console.log(JSON.stringify(getCanvasAgentAccessManifest()))"], { encoding: 'utf8' }));
const project = catalog.map(item=>({...item, authorized:item.group !== '分镜任务', previouslyExposed:false, execution:'server'}));
const utilities = ['getWeather','createDocument','updateDocument','requestSuggestions','loadProjectSkill'].map(id=>({id,title:id,group:'文档、天气与技能',authorized:true,previouslyExposed:false,entry:id,execution:'server'}));
const legacyAliases = JSON.parse(execFileSync(process.execPath, ['node_modules/tsx/dist/cli.mjs','-e', "import {LEGACY_AGENT_TOOL_MAP} from './lib/ai/canvas-agent-runtime/legacy-tool-map';console.log(JSON.stringify(LEGACY_AGENT_TOOL_MAP))"], { encoding: 'utf8' }));
const legacyNames = [...(await readFile('lib/ai/tools-registry.ts','utf8')).matchAll(/name:\s*"([^"]+)"/g)].map(match=>match[1]);
if (legacyNames.some(name=>!legacyAliases[name])) throw new Error('Legacy tool missing from authorization audit');
const report = { legacyAliases, policy:'图片画布及非视频项目业务已授权;视频画布与分镜暂不授权;底层用户权限与服务开关仍生效。管理、密钥、支付、身份认证、系统任务和Claw不属于创作Agent工具。',
upstreamCommit:'0a1fc56d279fd1001f78387bd81aec1c26767411', rows:[...canvas,...project,...utilities],
verifiedRoutes:Object.keys(contracts).length, deploymentOnly:['smart-edit.get'] };
await writeFile('docs/specs/zcode-canvas/authorization-audit.json',JSON.stringify(report,null,2)+'\n');
const esc=value=>String(value??'').replace(/[&<>"']/g,char=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[char]));
const rows=report.rows.map(row=>`<tr><td>${esc(row.workspace||row.group||'项目')}</td><td>${esc(row.title)}</td><td>${row.authorized?'已授权':'暂未授权'}</td><td>${esc(row.entry||row.id)}</td><td>${esc(row.note||row.hint||'')}</td></tr>`).join('');
await writeFile('docs/specs/zcode-canvas/authorization-audit.html',`<!doctype html><html lang="zh-CN"><meta charset="utf-8"><title>ZCode Agent 工具授权清单</title><style>body{font:14px system-ui;margin:32px;color:#222}table{border-collapse:collapse;width:100%}td,th{border:1px solid #ddd;padding:9px;text-align:left}th{background:#f4f4f4;position:sticky;top:0}tr:nth-child(even){background:#fafafa}</style><h1>ZCode Agent 工具授权清单</h1><p>${esc(report.policy)}</p><p>共 ${report.rows.length} 项能力;每一项均保留实际执行方式与限制。接口鉴权不等于业务结果已完成。</p><table><thead><tr><th>范围</th><th>能力</th><th>授权</th><th>工具 / 操作编号</th><th>执行限制</th></tr></thead><tbody>${rows}</tbody></table></html>`);
console.log(JSON.stringify({auditRows:report.rows.length, authorized:report.rows.filter(row=>row.authorized).length, paused:report.rows.filter(row=>!row.authorized).length, addedProjectOperations:project.filter(row=>row.authorized).length}));