Files
maestro/src/engine/tools/docs.ts
T
oss-sync b1292e34b2
CI / build-and-test (push) Has been cancelled
sync: update from private repo (edc775f2)
2026-07-06 01:04:12 +00:00

210 lines
8.2 KiB
TypeScript
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.
// docs.ts — ツール詳細ドキュメント参照用ツール
//
// リポジトリ内 docs/tools/{name}.md を読み込んで返す。
// ワークスペース外の固定パスから読むため、Read ツールでは到達できない。
// Tool description にこのツールへのポインタを書いておくと、
// 詳細な使い方を必要に応じてエージェントが取得できる。
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { ToolDef } from '../../llm/openai-compat.js';
import type { ToolContext, ToolResult } from './core.js';
import { logger } from '../../logger.js';
type McpToolLookup = (serverId: string, toolName: string) => { description: string | null; input_schema: string | null } | null;
let _mcpLookup: McpToolLookup | null = null;
export function setMcpToolLookup(fn: McpToolLookup | null): void {
_mcpLookup = fn;
}
// dist/engine/tools/docs.js または src/engine/tools/docs.ts から
// リポジトリルートを解決し、docs/tools/ を指す
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
const DOCS_DIR = path.join(REPO_ROOT, 'docs', 'tools');
// 関連ツールが同じ doc を参照できるようエイリアスを定義
// キー・値ともに小文字
const TOOL_DOC_ALIASES: Record<string, string> = {
// calendar.md にまとめる
addcalendarevent: 'calendar',
listcalendarevents: 'calendar',
// updateuseragents.md にまとめる
readuseragents: 'updateuseragents',
// checklist.md にまとめる
createchecklist: 'checklist',
checkitem: 'checklist',
getchecklist: 'checklist',
// x.ts ツールをまとめる
xuserposts: 'xsearch',
xpostdetail: 'xsearch',
xfetchcardmedia: 'xsearch',
xtimeline: 'xsearch',
// youtube.ts をまとめる
searchyoutube: 'getyoutubetranscript',
// maps.ts をまとめる
getdirections: 'searchplaces',
reversegeocode: 'searchplaces',
// ReadExcel/ReadDocx/ReadPdf/ReadPPTX/ReadMsg は Read に統合。旧名の ReadToolDoc は
// 統合後の read.md に解決する(後方互換)。
readpdf: 'read',
readexcel: 'read',
readdocx: 'read',
readpptx: 'read',
readmsg: 'read',
// office.ts の残存ツール (前処理系) は office.md にまとめる
pdftoimages: 'office',
splitexcelsheets: 'office',
splitdocxsections: 'office',
// skills.ts をまとめる (skills.md)
installskill: 'skills',
readskill: 'skills',
listskills: 'skills',
// pieces.ts をまとめる
getpiece: 'listpieces',
createpiece: 'listpieces',
updatepiece: 'listpieces',
// ms-learn.ts をまとめる
fetchmicrosoftlearn: 'searchmicrosoftlearn',
searchmicrosoftlearncache: 'searchmicrosoftlearn',
refreshmicrosoftlearncache: 'searchmicrosoftlearn',
// browser.ts: InteractiveBrowse / BrowseWithSession は browseweb.md にまとめる
interactivebrowse: 'browseweb',
browsewithsession: 'browseweb',
// 保存済みログインセッションの概念解説 (browse-sessions.md)。専用ツールは無いが、
// 自然な名前で ReadToolDoc から引けるよう alias。
browsersessions: 'browse-sessions',
browsersession: 'browse-sessions',
browsesessions: 'browse-sessions',
// ssh.ts をまとめる
sshexec: 'ssh-tools',
sshupload: 'ssh-tools',
sshdownload: 'ssh-tools',
sshlistconnections: 'ssh-tools',
// ssh-console.ts をまとめる
sshconsoleensure: 'ssh-console-tools',
sshconsolesend: 'ssh-console-tools',
sshconsolesnapshot: 'ssh-console-tools',
sshconsolerun: 'ssh-console-tools',
// slide.ts をまとめる
settheme: 'slide',
addslide: 'slide',
buildpptx: 'slide',
resetslides: 'slide',
// ワークスペース・アプリの作り方 (workspace-apps.md)。専用ツールは無いが、
// 「ワークスペース・アプリを作って」依頼時にエージェントが仕様を引けるよう alias。
workspaceapp: 'workspace-apps',
workspaceapps: 'workspace-apps',
createapp: 'workspace-apps',
buildapp: 'workspace-apps',
appbridge: 'workspace-apps',
// TestWorkspaceApp (testworkspaceapp.md)
testworkspaceapp: 'testworkspaceapp',
// orchestration.ts: WaitSubTask は spawnsubtask.md にまとめる
waitsubtask: 'spawnsubtask',
// file-provenance.ts: GetFileProvenance / ListWorkspaceFiles を1つの doc にまとめる
getfileprovenance: 'file-provenance',
listworkspacefiles: 'file-provenance',
};
const READ_TOOL_DOC_DEF: ToolDef = {
type: 'function',
function: {
name: 'ReadToolDoc',
description:
'ツールの詳細な使い方ドキュメント(docs/tools/{name}.md、リポジトリ内固定パス)を読み込む。各ツールの description は概要のみなので、詳しい手順や例が必要なときに呼ぶ。'
+ 'MCP ツール名(mcp__server__tool)を渡すと、キャッシュ済みの説明と入力スキーマを返す。',
parameters: {
type: 'object',
properties: {
name: {
type: 'string',
description: '読みたいツール名(例: "BrowseWeb", "WebFetch")',
},
},
required: ['name'],
},
},
};
export const TOOL_DEFS: Record<string, ToolDef> = {
ReadToolDoc: READ_TOOL_DOC_DEF,
};
export async function executeTool(
name: string,
input: Record<string, unknown>,
_ctx: ToolContext,
): Promise<ToolResult | null> {
if (name !== 'ReadToolDoc') return null;
const toolName = input['name'] as string | undefined;
if (!toolName || typeof toolName !== 'string') {
return { output: 'ReadToolDoc error: name パラメータが必要です', isError: true };
}
// MCP ツール名 (mcp__<serverId>__<toolName>) の場合はキャッシュから返す
if (toolName.startsWith('mcp__')) {
// Lazy-import to avoid a hard dependency on mcp module at module-load time.
const { parseToolName } = await import('../../mcp/tool-adapter.js');
const parsed = parseToolName(toolName);
if (!parsed) {
return { output: `ReadToolDoc: 不正な MCP ツール名 "${toolName}"`, isError: true };
}
if (!_mcpLookup) {
return { output: 'ReadToolDoc: MCP サブシステムが初期化されていません', isError: true };
}
const row = _mcpLookup(parsed.serverId, parsed.toolName);
if (!row) {
return { output: `ReadToolDoc: ${toolName} のキャッシュ情報がありません`, isError: false };
}
let schemaBlock = '';
if (row.input_schema) {
try {
const schema = JSON.parse(row.input_schema);
schemaBlock = `\n\n## Input schema\n\n\`\`\`json\n${JSON.stringify(schema, null, 2)}\n\`\`\``;
} catch {
schemaBlock = `\n\n## Input schema (raw)\n\n\`\`\`\n${row.input_schema}\n\`\`\``;
}
}
return {
output: `# ${toolName}\n\n${row.description ?? '(no description)'}${schemaBlock}`,
isError: false,
};
}
// パストラバーサル防止: 英数字とハイフン・アンダースコアのみ許可
if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(toolName)) {
return { output: `ReadToolDoc error: 不正なツール名 "${toolName}"`, isError: true };
}
const lowerName = toolName.toLowerCase();
const resolvedName = TOOL_DOC_ALIASES[lowerName] ?? lowerName;
const docPath = path.join(DOCS_DIR, `${resolvedName}.md`);
try {
const content = await fs.promises.readFile(docPath, 'utf-8');
return { output: content, isError: false };
} catch (e: any) {
if (e.code === 'ENOENT') {
// 利用可能なドキュメント一覧を返す
try {
const files = await fs.promises.readdir(DOCS_DIR);
const available = files
.filter((f) => f.endsWith('.md'))
.map((f) => f.replace(/\.md$/, ''))
.sort();
return {
output: `ReadToolDoc: "${toolName}" のドキュメントは存在しません。\n利用可能なドキュメント:\n${available.map((n) => `- ${n}`).join('\n')}`,
isError: true,
};
} catch {
return { output: `ReadToolDoc: "${toolName}" のドキュメントは存在しません。`, isError: true };
}
}
logger.warn(`[ReadToolDoc] failed to read ${docPath}: ${e.message}`);
return { output: `ReadToolDoc error: ${e.message}`, isError: true };
}
}