import { ToolDef } from '../../llm/openai-compat.js'; import { logger } from '../../logger.js'; import { ToolContext, ToolResult, ToolsConfig, ALL_TOOL_DEFS, getToolDefs as getCoreToolDefs, executeCoreTools, } from './core.js'; import { saveRawData, logRawDownload, RAW_SAVE_TOOLS, RAW_LOG_ONLY_TOOLS } from './raw-save.js'; import { saveStructuredBlocks } from './structured-blocks.js'; import type { McpAggregator } from '../../mcp/aggregator.js'; import type { McpRuntimeConfig } from '../../mcp/config.js'; export type { ToolContext, ToolResult, ToolsConfig }; let _mcpAggregator: McpAggregator | null = null; export function setMcpAggregator(agg: McpAggregator | null): void { _mcpAggregator = agg; } type ExecuteCtxWithMcp = ToolContext & { ownerId?: string | null; jobId?: string | null; mcpConfig?: McpRuntimeConfig; mcpQuotaState?: { files: number; bytes: number }; }; // 外部モジュール(他チームが実装)のインターフェース型 interface ToolModule { TOOL_DEFS: Record; executeTool(name: string, input: Record, ctx: ToolContext): Promise; } // 外部モジュールを動的にロード(存在しない場合は null) async function tryLoadModule(specifier: string): Promise { try { const mod = await import(specifier) as ToolModule; return mod; } catch { return null; } } // 外部モジュールのキャッシュ let _webModule: ToolModule | null | undefined = undefined; let _imageModule: ToolModule | null | undefined = undefined; let _dataModule: ToolModule | null | undefined = undefined; let _officeModule: ToolModule | null | undefined = undefined; let _reviewModule: ToolModule | null | undefined = undefined; let _xModule: ToolModule | null | undefined = undefined; let _orchestrationModule: ToolModule | null | undefined = undefined; let _browserModule: ToolModule | null | undefined = undefined; let _mapsModule: ToolModule | null | undefined = undefined; let _youtubeModule: ToolModule | null | undefined = undefined; let _piecesModule: ToolModule | null | undefined = undefined; let _amazonModule: ToolModule | null | undefined = undefined; let _speechModule: ToolModule | null | undefined = undefined; let _checklistModule: ToolModule | null | undefined = undefined; let _knowledgeModule: ToolModule | null | undefined = undefined; let _msLearnModule: ToolModule | null | undefined = undefined; let _slideModule: ToolModule | null | undefined = undefined; let _userFolderModule: ToolModule | null | undefined = undefined; async function getWebModule(): Promise { if (_webModule === undefined) { _webModule = await tryLoadModule('./web.js'); if (_webModule) logger.debug('[tools/index] web module loaded'); } return _webModule; } async function getImageModule(): Promise { if (_imageModule === undefined) { _imageModule = await tryLoadModule('./image.js'); if (_imageModule) logger.debug('[tools/index] image module loaded'); } return _imageModule; } async function getDataModule(): Promise { if (_dataModule === undefined) { _dataModule = await tryLoadModule('./data.js'); if (_dataModule) logger.debug('[tools/index] data module loaded'); } return _dataModule; } async function getOfficeModule(): Promise { if (_officeModule === undefined) { _officeModule = await tryLoadModule('./office.js'); if (_officeModule) logger.debug('[tools/index] office module loaded'); } return _officeModule; } async function getReviewModule(): Promise { if (_reviewModule === undefined) { _reviewModule = await tryLoadModule('./review.js'); if (_reviewModule) logger.debug('[tools/index] review module loaded'); } return _reviewModule; } async function getXModule(): Promise { if (_xModule === undefined) { _xModule = await tryLoadModule('./x.js'); if (_xModule) logger.debug('[tools/index] x module loaded'); } return _xModule; } async function getOrchestrationModule(): Promise { if (_orchestrationModule === undefined) { _orchestrationModule = await tryLoadModule('./orchestration.js'); if (_orchestrationModule) logger.debug('[tools/index] orchestration module loaded'); } return _orchestrationModule; } async function getBrowserModule(): Promise { if (_browserModule === undefined) { _browserModule = await tryLoadModule('./browser.js'); if (_browserModule) logger.debug('[tools/index] browser module loaded'); } return _browserModule; } async function getMapsModule(): Promise { if (_mapsModule === undefined) { _mapsModule = await tryLoadModule('./maps.js'); if (_mapsModule) logger.debug('[tools/index] maps module loaded'); } return _mapsModule; } async function getYoutubeModule(): Promise { if (_youtubeModule === undefined) { _youtubeModule = await tryLoadModule('./youtube.js'); if (_youtubeModule) logger.debug('[tools/index] youtube module loaded'); } return _youtubeModule; } async function getPiecesModule(): Promise { if (_piecesModule === undefined) { _piecesModule = await tryLoadModule('./pieces.js'); if (_piecesModule) logger.debug('[tools/index] pieces module loaded'); } return _piecesModule; } async function getAmazonModule(): Promise { if (_amazonModule === undefined) { _amazonModule = await tryLoadModule('./amazon.js'); if (_amazonModule) logger.debug('[tools/index] amazon module loaded'); } return _amazonModule; } async function getSpeechModule(): Promise { if (_speechModule === undefined) { _speechModule = await tryLoadModule('./speech.js'); if (_speechModule) logger.debug('[tools/index] speech module loaded'); } return _speechModule; } async function getChecklistModule(): Promise { if (_checklistModule === undefined) { _checklistModule = await tryLoadModule('./checklist.js'); if (_checklistModule) logger.debug('[tools/index] checklist module loaded'); } return _checklistModule; } async function getKnowledgeModule(): Promise { if (_knowledgeModule === undefined) { _knowledgeModule = await tryLoadModule('./knowledge.js'); if (_knowledgeModule) logger.debug('[tools/index] knowledge module loaded'); } return _knowledgeModule; } async function getSlideModule(): Promise { if (_slideModule === undefined) { _slideModule = await tryLoadModule('./slide.js'); if (_slideModule) logger.debug('[tools/index] slide module loaded'); } return _slideModule; } async function getMsLearnModule(): Promise { if (_msLearnModule === undefined) { _msLearnModule = await tryLoadModule('./ms-learn.js'); if (_msLearnModule) logger.debug('[tools/index] ms-learn module loaded'); } return _msLearnModule; } let _docsModule: ToolModule | null | undefined; async function getDocsModule(): Promise { if (_docsModule === undefined) { _docsModule = await tryLoadModule('./docs.js'); if (_docsModule) logger.debug('[tools/index] docs module loaded'); } return _docsModule; } let _missionModule: ToolModule | null | undefined; async function getMissionModule(): Promise { if (_missionModule === undefined) { _missionModule = await tryLoadModule('./mission.js'); if (_missionModule) logger.debug('[tools/index] mission module loaded'); } return _missionModule; } async function getUserFolderModule(): Promise { if (_userFolderModule === undefined) { _userFolderModule = await tryLoadModule('./user-folder.js'); if (_userFolderModule) logger.debug('[tools/index] user-folder module loaded'); } return _userFolderModule; } let _brainstormModule: ToolModule | null | undefined; async function getBrainstormModule(): Promise { if (_brainstormModule === undefined) { _brainstormModule = await tryLoadModule('./brainstorm.js'); if (_brainstormModule) logger.debug('[tools/index] brainstorm module loaded'); } return _brainstormModule; } let _appDocsModule: ToolModule | null | undefined; async function getAppDocsModule(): Promise { if (_appDocsModule === undefined) { _appDocsModule = await tryLoadModule('./app-docs.js'); if (_appDocsModule) logger.debug('[tools/index] app-docs module loaded'); } return _appDocsModule; } let _sshModule: ToolModule | null | undefined; async function getSshModule(): Promise { if (_sshModule === undefined) { _sshModule = await tryLoadModule('./ssh.js'); if (_sshModule) logger.debug('[tools/index] ssh module loaded'); } return _sshModule; } let _sshConsoleModule: ToolModule | null | undefined; async function getSshConsoleModule(): Promise { if (_sshConsoleModule === undefined) { _sshConsoleModule = await tryLoadModule('./ssh-console.js'); if (_sshConsoleModule) logger.debug('[tools/index] ssh-console module loaded'); } return _sshConsoleModule; } let _notesModule: ToolModule | null | undefined; async function getNotesModule(): Promise { if (_notesModule === undefined) { _notesModule = await tryLoadModule('./notes.js'); if (_notesModule) logger.debug('[tools/index] notes module loaded'); } return _notesModule; } let _dashboardModule: ToolModule | null | undefined = undefined; async function getDashboardModule(): Promise { if (_dashboardModule === undefined) { _dashboardModule = await tryLoadModule('./dashboard.js'); if (_dashboardModule) logger.debug('[tools/index] dashboard module loaded'); } return _dashboardModule; } /** * 全モジュールのツール定義を統合して返す。 * allowedTools と editAllowed に応じてフィルタリングする。 */ export async function getToolDefs( allowedTools: string[], editAllowed: boolean, options?: { vlmEnabled?: boolean; ownerId?: string | null; mcpDisabled?: boolean }, ): Promise { // 全ツール定義を収集 const allDefs: Record = { ...ALL_TOOL_DEFS }; const webMod = await getWebModule(); if (webMod) Object.assign(allDefs, webMod.TOOL_DEFS); const imageMod = await getImageModule(); if (imageMod) Object.assign(allDefs, imageMod.TOOL_DEFS); const dataMod = await getDataModule(); if (dataMod) Object.assign(allDefs, dataMod.TOOL_DEFS); const officeMod = await getOfficeModule(); if (officeMod) Object.assign(allDefs, officeMod.TOOL_DEFS); const reviewMod = await getReviewModule(); if (reviewMod) Object.assign(allDefs, reviewMod.TOOL_DEFS); const xMod = await getXModule(); if (xMod) Object.assign(allDefs, xMod.TOOL_DEFS); const orchestrationMod = await getOrchestrationModule(); if (orchestrationMod) Object.assign(allDefs, orchestrationMod.TOOL_DEFS); const browserMod = await getBrowserModule(); if (browserMod) Object.assign(allDefs, browserMod.TOOL_DEFS); const mapsMod = await getMapsModule(); if (mapsMod) Object.assign(allDefs, mapsMod.TOOL_DEFS); const youtubeMod = await getYoutubeModule(); if (youtubeMod) Object.assign(allDefs, youtubeMod.TOOL_DEFS); const piecesMod = await getPiecesModule(); if (piecesMod) Object.assign(allDefs, piecesMod.TOOL_DEFS); const amazonMod = await getAmazonModule(); if (amazonMod) Object.assign(allDefs, amazonMod.TOOL_DEFS); const speechMod = await getSpeechModule(); if (speechMod) Object.assign(allDefs, speechMod.TOOL_DEFS); const checklistMod = await getChecklistModule(); if (checklistMod) Object.assign(allDefs, checklistMod.TOOL_DEFS); const knowledgeMod = await getKnowledgeModule(); if (knowledgeMod) Object.assign(allDefs, knowledgeMod.TOOL_DEFS); const msLearnMod = await getMsLearnModule(); if (msLearnMod) Object.assign(allDefs, msLearnMod.TOOL_DEFS); const slideMod = await getSlideModule(); if (slideMod) Object.assign(allDefs, slideMod.TOOL_DEFS); const docsMod = await getDocsModule(); if (docsMod) Object.assign(allDefs, docsMod.TOOL_DEFS); const missionMod = await getMissionModule(); if (missionMod) Object.assign(allDefs, missionMod.TOOL_DEFS); const userFolderMod = await getUserFolderModule(); if (userFolderMod) Object.assign(allDefs, userFolderMod.TOOL_DEFS); const brainstormMod = await getBrainstormModule(); if (brainstormMod) Object.assign(allDefs, brainstormMod.TOOL_DEFS); const appDocsMod = await getAppDocsModule(); if (appDocsMod) Object.assign(allDefs, appDocsMod.TOOL_DEFS); const sshMod = await getSshModule(); if (sshMod) Object.assign(allDefs, sshMod.TOOL_DEFS); const sshConsoleMod = await getSshConsoleModule(); if (sshConsoleMod) Object.assign(allDefs, sshConsoleMod.TOOL_DEFS); const notesMod = await getNotesModule(); if (notesMod) Object.assign(allDefs, notesMod.TOOL_DEFS); const dashboardMod = await getDashboardModule(); if (dashboardMod) Object.assign(allDefs, dashboardMod.TOOL_DEFS); const { TOOL_DEFS: skillToolDefs } = await import('./skills.js'); Object.assign(allDefs, skillToolDefs); // メタツール: piece の allowed_tools に書かれていなくても常に利用可能 // - ReadToolDoc: 全ツールのドキュメント参照 // - CreateChecklist / CheckItem / GetChecklist: 進捗管理 (複数ステップタスクで使用) // - MissionUpdate: タスクの目標 / 進捗のピン止めメモを更新 (会話が長くなって // 最初の要件を見失わないため。常時上書き可能、未指定フィールドは保持) // - ListUserAssets / RunUserScript: ユーザーフォルダのスクリプト探索・実行 // - Brainstorm: 着手前 or 行き詰まり時の多アプローチ比較 (issue #247) // - ReadAppDoc / ListAppDocs / GetMyOrchestratorState: Help アシスタント用 (#help piece) // ただし他の piece からも参照できるようにメタ扱い const META_TOOLS = ['ReadToolDoc', 'CreateChecklist', 'CheckItem', 'GetChecklist', 'MissionUpdate', 'ListUserAssets', 'RunUserScript', 'UpdateUserMemory', 'ReadUserMemory', 'ReadUserTemplate', 'RenderUserTemplate', 'WriteUserScript', 'WriteUserTemplate', 'Brainstorm', 'ReadAppDoc', 'ListAppDocs', 'GetMyOrchestratorState', 'ReadSkill', 'ListSkills', 'InstallSkill']; const effectiveAllowed = [...allowedTools]; for (const meta of META_TOOLS) { if (!effectiveAllowed.includes(meta) && meta in allDefs) { effectiveAllowed.push(meta); } } const staticDefs = effectiveAllowed .filter((name) => { if (!editAllowed && (name === 'Write' || name === 'Edit')) return false; if (!options?.vlmEnabled && name === 'ReadImage') return false; return name in allDefs; }) .map((name) => allDefs[name]!); const mcpDefs = _mcpAggregator && options?.ownerId && !options?.mcpDisabled ? await _mcpAggregator.getToolDefs(options.ownerId, allowedTools) : []; return [...staticDefs, ...mcpDefs]; } /** * ツールを実行する内部ルーター。 * core → web → image → data の順で各モジュールに委譲し、 * 最初に null でない結果を返したモジュールの結果を使う。 */ async function executeToolInner( name: string, input: Record, ctx: ToolContext, ): Promise { if (name.startsWith('mcp__')) { if (ctx.mcpDisabled) { return { output: 'MCP tools are disabled for this task (options.mcpDisabled)', isError: true }; } if (!_mcpAggregator) { return { output: 'MCP サブシステムが初期化されていません', isError: true }; } const ctxWithMcp = ctx as ExecuteCtxWithMcp; if (!ctxWithMcp.mcpQuotaState) { ctxWithMcp.mcpQuotaState = { files: 0, bytes: 0 }; } if (!ctxWithMcp.mcpConfig) { return { output: 'MCP 設定が ToolContext に伝播していません', isError: true }; } const result = await _mcpAggregator.executeTool(name, input, { workspacePath: ctx.workspacePath, ownerId: ctxWithMcp.ownerId ?? '', jobId: ctxWithMcp.jobId ?? '', config: ctxWithMcp.mcpConfig, quotaState: ctxWithMcp.mcpQuotaState, }); if (result === null) { return { output: `MCP ツール dispatch が ${name} に対して null を返しました`, isError: true }; } return { output: result.output, isError: result.isError }; } // core ツール const coreResult = await executeCoreTools(name, input, ctx); if (coreResult !== null) return coreResult; // web ツール const webMod = await getWebModule(); if (webMod) { const webResult = await webMod.executeTool(name, input, ctx); if (webResult !== null) return webResult; } // image ツール const imageMod = await getImageModule(); if (imageMod) { const imageResult = await imageMod.executeTool(name, input, ctx); if (imageResult !== null) return imageResult; } // data ツール const dataMod = await getDataModule(); if (dataMod) { const dataResult = await dataMod.executeTool(name, input, ctx); if (dataResult !== null) return dataResult; } // office ツール const officeMod = await getOfficeModule(); if (officeMod) { const officeResult = await officeMod.executeTool(name, input, ctx); if (officeResult !== null) return officeResult; } // review ツール const reviewMod = await getReviewModule(); if (reviewMod) { const reviewResult = await reviewMod.executeTool(name, input, ctx); if (reviewResult !== null) return reviewResult; } // x tools const xMod = await getXModule(); if (xMod) { const xResult = await xMod.executeTool(name, input, ctx); if (xResult !== null) return xResult; } // orchestration ツール const orchestrationMod = await getOrchestrationModule(); if (orchestrationMod) { const orchestrationResult = await orchestrationMod.executeTool(name, input, ctx); if (orchestrationResult !== null) return orchestrationResult; } // browser ツール const browserMod = await getBrowserModule(); if (browserMod) { const browserResult = await browserMod.executeTool(name, input, ctx); if (browserResult !== null) return browserResult; } // maps ツール const mapsMod = await getMapsModule(); if (mapsMod) { const mapsResult = await mapsMod.executeTool(name, input, ctx); if (mapsResult !== null) return mapsResult; } // youtube ツール const youtubeMod = await getYoutubeModule(); if (youtubeMod) { const youtubeResult = await youtubeMod.executeTool(name, input, ctx); if (youtubeResult !== null) return youtubeResult; } // pieces ツール const piecesMod = await getPiecesModule(); if (piecesMod) { const piecesResult = await piecesMod.executeTool(name, input, ctx); if (piecesResult !== null) return piecesResult; } // amazon ツール const amazonMod = await getAmazonModule(); if (amazonMod) { const amazonResult = await amazonMod.executeTool(name, input, ctx); if (amazonResult !== null) return amazonResult; } // speech ツール const speechMod = await getSpeechModule(); if (speechMod) { const speechResult = await speechMod.executeTool(name, input, ctx); if (speechResult !== null) return speechResult; } // checklist ツール const checklistMod = await getChecklistModule(); if (checklistMod) { const checklistResult = await checklistMod.executeTool(name, input, ctx); if (checklistResult !== null) return checklistResult; } // knowledge ツール const knowledgeMod = await getKnowledgeModule(); if (knowledgeMod) { const knowledgeResult = await knowledgeMod.executeTool(name, input, ctx); if (knowledgeResult !== null) return knowledgeResult; } // ms-learn ツール const msLearnMod = await getMsLearnModule(); if (msLearnMod) { const msLearnResult = await msLearnMod.executeTool(name, input, ctx); if (msLearnResult !== null) return msLearnResult; } // slide ツール (SetTheme / AddSlide / BuildPptx / ResetSlides) const slideMod = await getSlideModule(); if (slideMod) { const slideResult = await slideMod.executeTool(name, input, ctx); if (slideResult !== null) return slideResult; } // docs ツール (ReadToolDoc) const docsMod = await getDocsModule(); if (docsMod) { const docsResult = await docsMod.executeTool(name, input, ctx); if (docsResult !== null) return docsResult; } // mission ツール (MissionUpdate) const missionMod = await getMissionModule(); if (missionMod) { const missionResult = await missionMod.executeTool(name, input, ctx); if (missionResult !== null) return missionResult; } // user-folder ツール (ListUserAssets / RunUserScript) const userFolderMod = await getUserFolderModule(); if (userFolderMod) { const userFolderResult = await userFolderMod.executeTool(name, input, ctx); if (userFolderResult !== null) return userFolderResult; } // brainstorm ツール (Brainstorm) const brainstormMod = await getBrainstormModule(); if (brainstormMod) { const brainstormResult = await brainstormMod.executeTool(name, input, ctx); if (brainstormResult !== null) return brainstormResult; } // app-docs ツール (ReadAppDoc / ListAppDocs / GetMyOrchestratorState) const appDocsMod = await getAppDocsModule(); if (appDocsMod) { const appDocsResult = await appDocsMod.executeTool(name, input, ctx); if (appDocsResult !== null) return appDocsResult; } // ssh ツール (SshExec / SshUpload / SshDownload) const sshMod = await getSshModule(); if (sshMod) { const sshResult = await sshMod.executeTool(name, input, ctx); if (sshResult !== null) return sshResult; } // ssh-console ツール (SshConsoleEnsure / Send / Snapshot) const sshConsoleMod = await getSshConsoleModule(); if (sshConsoleMod) { const sshConsoleResult = await sshConsoleMod.executeTool(name, input, ctx); if (sshConsoleResult !== null) return sshConsoleResult; } // notes ツール (SearchNotes / ReadNote / WriteNote) const notesMod = await getNotesModule(); if (notesMod) { const notesResult = await notesMod.executeTool(name, input, ctx); if (notesResult !== null) return notesResult; } // dashboard ツール (UpdateDashboardWidget) const dashboardMod = await getDashboardModule(); if (dashboardMod) { const dashboardResult = await dashboardMod.executeTool(name, input, ctx); if (dashboardResult !== null) return dashboardResult; } // skills ツール (ReadSkill) const { executeSkillTool } = await import('./skills.js'); const skillResult = executeSkillTool(name, input, ctx); if (skillResult !== null) return skillResult; return { output: `Unknown tool: ${name}`, isError: true }; } /** * ツールを実行するルーター(生データ自動保存ラッパー付き)。 * executeToolInner に委譲した後、成功した対象ツールの結果を logs/raw/ に保存する。 */ export async function executeTool( name: string, input: Record, ctx: ToolContext, ): Promise { logger.debug(`[tools/index] executing ${name}`); const result = await executeToolInner(name, input, ctx); // raw保存ラッパー: 成功した対象ツールの結果を logs/raw/ に保存 if (ctx.workspacePath && !result.isError) { if (RAW_SAVE_TOOLS.has(name)) { saveRawData(ctx.workspacePath, name, result.output); } else if (RAW_LOG_ONLY_TOOLS.has(name)) { const pathMatch = result.output.match(/-> (.+?) \(/); if (pathMatch?.[1]) { logRawDownload(ctx.workspacePath, name, pathMatch[1], result.output.length); } } // 構造化データ保存: structuredBlocks があれば logs/structured/ に保存 if (result.structuredBlocks?.length) { saveStructuredBlocks(ctx.workspacePath, result.structuredBlocks); } } return result; } // 同期版 getToolDefs のラッパー(後方互換のため、コアツールのみを返す同期版) export { getCoreToolDefs as getCoreToolDefs };