

















@@ -1,12 +1,10 @@
11import fs from "node:fs";
22import path from "node:path";
33import { fileURLToPath } from "node:url";
4-import ts from "typescript";
54import { TOOL_DISPLAY_CONFIG, type ToolDisplayConfig } from "../src/agents/tool-display-config.js";
6576const scriptDir = path.dirname(fileURLToPath(import.meta.url));
87const repoRoot = path.resolve(scriptDir, "..");
9-const configPath = path.join(repoRoot, "src/agents/tool-display-config.ts");
108const outputPath = path.join(
119repoRoot,
1210"apps/shared/OpenClawKit/Sources/OpenClawKit/Resources/tool-display.json",
@@ -18,63 +16,49 @@ const toolSources = [
1816path.join(repoRoot, "src/auto-reply/reply/acp-projector.ts"),
1917];
201821-type DuplicateToolKey = {
22-name: string;
23-lines: number[];
24-};
19+const args = new Set(process.argv.slice(2));
20+const shouldCheck = args.has("--check");
21+const shouldWrite = args.has("--write");
252226-export function main(argv = process.argv.slice(2)): number {
27-const args = new Set(argv);
28-const shouldCheck = args.has("--check");
29-const shouldWrite = args.has("--write");
30-31-if (!shouldCheck && !shouldWrite) {
32-console.error("Usage: node --import tsx scripts/tool-display.ts --check|--write");
33-return 1;
34-}
35-36-const duplicateErrors = collectToolDisplayDuplicateErrors({ includeSnapshot: shouldCheck });
37-if (duplicateErrors.length > 0) {
38-console.error(duplicateErrors.join("\n"));
39-return 1;
40-}
41-42-const expected = serializeToolDisplayConfig();
43-ensureCoreToolCoverage();
23+if (!shouldCheck && !shouldWrite) {
24+console.error("Usage: node --import tsx scripts/tool-display.ts --check|--write");
25+process.exit(1);
26+}
442745-if (shouldWrite) {
46-fs.mkdirSync(path.dirname(outputPath), { recursive: true });
47-fs.writeFileSync(outputPath, expected);
48-process.stdout.write(`wrote ${path.relative(repoRoot, outputPath)}\n`);
49-return 0;
50-}
28+const expected = serializeToolDisplayConfig();
29+ensureCoreToolCoverage();
513052- if (!fs.existsSync(path.dirname(outputPath))) {
53- process.stdout.write(
54- `skip tool-display snapshot check; missing ${path.relative(repoRoot, path.dirname(outputPath))}\n`,
55- );
56- return 0;
57- }
31+if (shouldWrite) {
32+fs.mkdirSync(path.dirname(outputPath), { recursive: true });
33+fs.writeFileSync(outputPath, expected);
34+process.stdout.write(`wrote ${path.relative(repoRoot, outputPath)}\n`);
35+process.exit(0);
36+}
583759- if (!fs.existsSync(outputPath)) {
60- console.error(
61- `missing generated snapshot: ${path.relative(repoRoot, outputPath)}\nrun: pnpm tool-display:write`,
62- );
63- return 1;
64- }
38+if (!fs.existsSync(path.dirname(outputPath))) {
39+process.stdout.write(
40+`skip tool-display snapshot check; missing ${path.relative(repoRoot, path.dirname(outputPath))}\n`,
41+);
42+process.exit(0);
43+}
654466-const actual = fs.readFileSync(outputPath, "utf8");
67-if (actual !== expected) {
68-console.error(
69-`tool-display snapshot is stale: ${path.relative(repoRoot, outputPath)}\nrun: pnpm tool-display:write`,
70-);
71-return 1;
72-}
45+if (!fs.existsSync(outputPath)) {
46+console.error(
47+`missing generated snapshot: ${path.relative(repoRoot, outputPath)}\nrun: pnpm tool-display:write`,
48+);
49+process.exit(1);
50+}
735174-process.stdout.write("tool-display snapshot is up to date\n");
75-return 0;
52+const actual = fs.readFileSync(outputPath, "utf8");
53+if (actual !== expected) {
54+console.error(
55+`tool-display snapshot is stale: ${path.relative(repoRoot, outputPath)}\nrun: pnpm tool-display:write`,
56+);
57+process.exit(1);
7658}
775960+process.stdout.write("tool-display snapshot is up to date\n");
61+7862function ensureCoreToolCoverage() {
7963const toolNames = new Set<string>();
8064for (const sourcePath of toolSources) {
@@ -108,131 +92,3 @@ function collectToolNamesFromFile(sourcePath: string, names: Set<string>) {
10892function serializeToolDisplayConfig(config: ToolDisplayConfig = TOOL_DISPLAY_CONFIG): string {
10993return `${JSON.stringify(config, null, 2)}\n`;
11094}
111-112-function collectToolDisplayDuplicateErrors(options: { includeSnapshot: boolean }): string[] {
113-const duplicateErrors: string[] = [];
114-const configSource = fs.readFileSync(configPath, "utf8");
115-const configDuplicates = collectToolDisplayConfigDuplicateKeys(configSource, configPath);
116-if (configDuplicates.length > 0) {
117-duplicateErrors.push(
118-formatDuplicateToolKeyError(path.relative(repoRoot, configPath), configDuplicates),
119-);
120-}
121-122-if (options.includeSnapshot && fs.existsSync(outputPath)) {
123-const snapshotSource = fs.readFileSync(outputPath, "utf8");
124-const snapshotDuplicates = collectToolDisplaySnapshotDuplicateKeys(snapshotSource, outputPath);
125-if (snapshotDuplicates.length > 0) {
126-duplicateErrors.push(
127-formatDuplicateToolKeyError(path.relative(repoRoot, outputPath), snapshotDuplicates),
128-);
129-}
130-}
131-return duplicateErrors;
132-}
133-134-export function collectToolDisplayConfigDuplicateKeys(
135-source: string,
136-sourcePath = "src/agents/tool-display-config.ts",
137-): DuplicateToolKey[] {
138-const sourceFile = ts.createSourceFile(sourcePath, source, ts.ScriptTarget.Latest, true);
139-let toolsObject: ts.ObjectLiteralExpression | undefined;
140-visitToolDisplayConfig(sourceFile, (configObject) => {
141-toolsObject = findObjectProperty(configObject, "tools");
142-});
143-return toolsObject ? collectDuplicatePropertyKeys(toolsObject, sourceFile) : [];
144-}
145-146-export function collectToolDisplaySnapshotDuplicateKeys(
147-source: string,
148-sourcePath = "tool-display.json",
149-): DuplicateToolKey[] {
150-const sourceFile = ts.parseJsonText(sourcePath, source);
151-const statement = sourceFile.statements[0];
152-if (!statement || !ts.isExpressionStatement(statement)) {
153-return [];
154-}
155-const root = statement.expression;
156-if (!ts.isObjectLiteralExpression(root)) {
157-return [];
158-}
159-const toolsObject = findObjectProperty(root, "tools");
160-return toolsObject ? collectDuplicatePropertyKeys(toolsObject, sourceFile) : [];
161-}
162-163-export function formatDuplicateToolKeyError(
164-relativePath: string,
165-duplicates: DuplicateToolKey[],
166-): string {
167-const formatted = duplicates
168-.map((duplicate) => `${duplicate.name} at lines ${duplicate.lines.join(", ")}`)
169-.join("; ");
170-return `tool-display metadata has duplicate tool ids in ${relativePath}: ${formatted}`;
171-}
172-173-function visitToolDisplayConfig(
174-node: ts.Node,
175-onConfig: (configObject: ts.ObjectLiteralExpression) => void,
176-) {
177-if (
178-ts.isVariableDeclaration(node) &&
179-ts.isIdentifier(node.name) &&
180-node.name.text === "TOOL_DISPLAY_CONFIG" &&
181-node.initializer &&
182-ts.isObjectLiteralExpression(node.initializer)
183-) {
184-onConfig(node.initializer);
185-return;
186-}
187-ts.forEachChild(node, (child) => visitToolDisplayConfig(child, onConfig));
188-}
189-190-function findObjectProperty(
191-object: ts.ObjectLiteralExpression,
192-propertyName: string,
193-): ts.ObjectLiteralExpression | undefined {
194-for (const property of object.properties) {
195-if (
196-ts.isPropertyAssignment(property) &&
197-getPropertyNameText(property.name) === propertyName &&
198-ts.isObjectLiteralExpression(property.initializer)
199-) {
200-return property.initializer;
201-}
202-}
203-return undefined;
204-}
205-206-function collectDuplicatePropertyKeys(
207-object: ts.ObjectLiteralExpression,
208-sourceFile: ts.SourceFile,
209-): DuplicateToolKey[] {
210-const keyLines = new Map<string, number[]>();
211-for (const property of object.properties) {
212-if (!ts.isPropertyAssignment(property)) {
213-continue;
214-}
215-const name = getPropertyNameText(property.name);
216-if (!name) {
217-continue;
218-}
219-const line =
220-sourceFile.getLineAndCharacterOfPosition(property.name.getStart(sourceFile)).line + 1;
221-keyLines.set(name, [...(keyLines.get(name) ?? []), line]);
222-}
223-return [...keyLines.entries()]
224-.filter(([, lines]) => lines.length > 1)
225-.map(([name, lines]) => ({ name, lines }))
226-.toSorted((left, right) => left.name.localeCompare(right.name));
227-}
228-229-function getPropertyNameText(name: ts.PropertyName): string | undefined {
230-if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
231-return name.text;
232-}
233-return undefined;
234-}
235-236-if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
237-process.exitCode = main();
238-}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。