45 lines
5.2 KiB
JavaScript
45 lines
5.2 KiB
JavaScript
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=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[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}));
|