Files
maestro/src/engine/tools/index.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

766 lines
29 KiB
TypeScript

import { ToolDef } from '../../llm/openai-compat.js';
import { logger } from '../../logger.js';
import {
ToolContext,
ToolResult,
ToolsConfig,
ALL_TOOL_DEFS,
executeCoreTools,
runtimeLogsDir,
} 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<string, ToolDef>;
executeTool(name: string, input: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult | null>;
}
// 外部モジュールを動的にロード(存在しない場合は null)
async function tryLoadModule(specifier: string): Promise<ToolModule | null> {
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 _msLearnModule: ToolModule | null | undefined = undefined;
let _slideModule: ToolModule | null | undefined = undefined;
let _userFolderModule: ToolModule | null | undefined = undefined;
let _appTestModule: ToolModule | null | undefined = undefined;
async function getWebModule(): Promise<ToolModule | null> {
if (_webModule === undefined) {
_webModule = await tryLoadModule('./web.js');
if (_webModule) logger.debug('[tools/index] web module loaded');
}
return _webModule;
}
async function getImageModule(): Promise<ToolModule | null> {
if (_imageModule === undefined) {
_imageModule = await tryLoadModule('./image.js');
if (_imageModule) logger.debug('[tools/index] image module loaded');
}
return _imageModule;
}
async function getDataModule(): Promise<ToolModule | null> {
if (_dataModule === undefined) {
_dataModule = await tryLoadModule('./data.js');
if (_dataModule) logger.debug('[tools/index] data module loaded');
}
return _dataModule;
}
async function getOfficeModule(): Promise<ToolModule | null> {
if (_officeModule === undefined) {
_officeModule = await tryLoadModule('./office.js');
if (_officeModule) logger.debug('[tools/index] office module loaded');
}
return _officeModule;
}
async function getReviewModule(): Promise<ToolModule | null> {
if (_reviewModule === undefined) {
_reviewModule = await tryLoadModule('./review.js');
if (_reviewModule) logger.debug('[tools/index] review module loaded');
}
return _reviewModule;
}
async function getXModule(): Promise<ToolModule | null> {
if (_xModule === undefined) {
_xModule = await tryLoadModule('./x.js');
if (_xModule) logger.debug('[tools/index] x module loaded');
}
return _xModule;
}
async function getOrchestrationModule(): Promise<ToolModule | null> {
if (_orchestrationModule === undefined) {
_orchestrationModule = await tryLoadModule('./orchestration.js');
if (_orchestrationModule) logger.debug('[tools/index] orchestration module loaded');
}
return _orchestrationModule;
}
async function getBrowserModule(): Promise<ToolModule | null> {
if (_browserModule === undefined) {
_browserModule = await tryLoadModule('./browser.js');
if (_browserModule) logger.debug('[tools/index] browser module loaded');
}
return _browserModule;
}
async function getMapsModule(): Promise<ToolModule | null> {
if (_mapsModule === undefined) {
_mapsModule = await tryLoadModule('./maps.js');
if (_mapsModule) logger.debug('[tools/index] maps module loaded');
}
return _mapsModule;
}
async function getYoutubeModule(): Promise<ToolModule | null> {
if (_youtubeModule === undefined) {
_youtubeModule = await tryLoadModule('./youtube.js');
if (_youtubeModule) logger.debug('[tools/index] youtube module loaded');
}
return _youtubeModule;
}
async function getPiecesModule(): Promise<ToolModule | null> {
if (_piecesModule === undefined) {
_piecesModule = await tryLoadModule('./pieces.js');
if (_piecesModule) logger.debug('[tools/index] pieces module loaded');
}
return _piecesModule;
}
async function getAmazonModule(): Promise<ToolModule | null> {
if (_amazonModule === undefined) {
_amazonModule = await tryLoadModule('./amazon.js');
if (_amazonModule) logger.debug('[tools/index] amazon module loaded');
}
return _amazonModule;
}
async function getSpeechModule(): Promise<ToolModule | null> {
if (_speechModule === undefined) {
_speechModule = await tryLoadModule('./speech.js');
if (_speechModule) logger.debug('[tools/index] speech module loaded');
}
return _speechModule;
}
async function getChecklistModule(): Promise<ToolModule | null> {
if (_checklistModule === undefined) {
_checklistModule = await tryLoadModule('./checklist.js');
if (_checklistModule) logger.debug('[tools/index] checklist module loaded');
}
return _checklistModule;
}
async function getSlideModule(): Promise<ToolModule | null> {
if (_slideModule === undefined) {
_slideModule = await tryLoadModule('./slide.js');
if (_slideModule) logger.debug('[tools/index] slide module loaded');
}
return _slideModule;
}
async function getMsLearnModule(): Promise<ToolModule | null> {
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<ToolModule | null> {
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<ToolModule | null> {
if (_missionModule === undefined) {
_missionModule = await tryLoadModule('./mission.js');
if (_missionModule) logger.debug('[tools/index] mission module loaded');
}
return _missionModule;
}
let _taskConversationModule: ToolModule | null | undefined;
async function getTaskConversationModule(): Promise<ToolModule | null> {
if (_taskConversationModule === undefined) {
_taskConversationModule = await tryLoadModule('./task-conversation.js');
if (_taskConversationModule) logger.debug('[tools/index] task-conversation module loaded');
}
return _taskConversationModule;
}
let _workspaceTaskSearchModule: ToolModule | null | undefined;
async function getWorkspaceTaskSearchModule(): Promise<ToolModule | null> {
if (_workspaceTaskSearchModule === undefined) {
_workspaceTaskSearchModule = await tryLoadModule('./workspace-task-search.js');
if (_workspaceTaskSearchModule) logger.debug('[tools/index] workspace-task-search module loaded');
}
return _workspaceTaskSearchModule;
}
let _fileProvenanceModule: ToolModule | null | undefined;
async function getFileProvenanceModule(): Promise<ToolModule | null> {
if (_fileProvenanceModule === undefined) {
_fileProvenanceModule = await tryLoadModule('./file-provenance.js');
if (_fileProvenanceModule) logger.debug('[tools/index] file-provenance module loaded');
}
return _fileProvenanceModule;
}
let _toolRequestModule: ToolModule | null | undefined;
async function getToolRequestModule(): Promise<ToolModule | null> {
if (_toolRequestModule === undefined) {
_toolRequestModule = await tryLoadModule('./tool-request.js');
if (_toolRequestModule) logger.debug('[tools/index] tool-request module loaded');
}
return _toolRequestModule;
}
/**
* Snapshot of all known static tool names, refreshed every getToolDefs() call
* (which runs at movement start, before any tool executes). Injected into
* RequestTool's ctx so it can classify an unknown tool name vs one that exists
* but isn't allowed in the current movement. MCP names are handled separately
* by RequestTool via the `mcp__` prefix, so they need not be in this set.
*/
let _allKnownToolNames: string[] = [];
async function getUserFolderModule(): Promise<ToolModule | null> {
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<ToolModule | null> {
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<ToolModule | null> {
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<ToolModule | null> {
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<ToolModule | null> {
if (_sshConsoleModule === undefined) {
_sshConsoleModule = await tryLoadModule('./ssh-console.js');
if (_sshConsoleModule) logger.debug('[tools/index] ssh-console module loaded');
}
return _sshConsoleModule;
}
let _calendarModule: ToolModule | null | undefined = undefined;
async function getCalendarModule(): Promise<ToolModule | null> {
if (_calendarModule === undefined) {
_calendarModule = await tryLoadModule('./calendar.js');
if (_calendarModule) logger.debug('[tools/index] calendar module loaded');
}
return _calendarModule;
}
async function getAppTestModule(): Promise<ToolModule | null> {
if (_appTestModule === undefined) {
_appTestModule = await tryLoadModule('./app-test.js');
if (_appTestModule) logger.debug('[tools/index] app-test module loaded');
}
return _appTestModule;
}
/**
* 全モジュールのツール定義を統合して返す。
* allowedTools と editAllowed に応じてフィルタリングする。
*/
export async function getToolDefs(
allowedTools: string[],
editAllowed: boolean,
options?: { vlmEnabled?: boolean; ownerId?: string | null; mcpDisabled?: boolean; spaceId?: string | null },
): Promise<ToolDef[]> {
// 全ツール定義を収集
const allDefs: Record<string, ToolDef> = { ...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 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 taskConversationMod = await getTaskConversationModule();
if (taskConversationMod) Object.assign(allDefs, taskConversationMod.TOOL_DEFS);
const workspaceTaskSearchMod = await getWorkspaceTaskSearchModule();
if (workspaceTaskSearchMod) Object.assign(allDefs, workspaceTaskSearchMod.TOOL_DEFS);
const fileProvenanceMod = await getFileProvenanceModule();
if (fileProvenanceMod) Object.assign(allDefs, fileProvenanceMod.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 calendarMod = await getCalendarModule();
if (calendarMod) Object.assign(allDefs, calendarMod.TOOL_DEFS);
const appTestMod = await getAppTestModule();
if (appTestMod) Object.assign(allDefs, appTestMod.TOOL_DEFS);
const { TOOL_DEFS: skillToolDefs } = await import('./skills.js');
Object.assign(allDefs, skillToolDefs);
const toolRequestMod = await getToolRequestModule();
if (toolRequestMod) Object.assign(allDefs, toolRequestMod.TOOL_DEFS);
// Refresh the known-tool snapshot for RequestTool classification.
//
// CONCURRENCY INVARIANT (must hold): this is taken HERE — after every static
// module is merged into `allDefs`, but BEFORE the per-job MCP defs are
// appended below. The static catalog does NOT depend on movement / ownerId /
// spaceId / mcpDisabled, so every concurrent getToolDefs() writer produces an
// IDENTICAL array. That is why this module-global is safe to read from a
// different movement's RequestTool dispatch. If you ever make a STATIC tool's
// presence in `allDefs` conditional (feature flag, per-space, etc.), this
// snapshot becomes movement-dependent and the global must move into ctx
// instead — otherwise concurrent movements would misclassify each other's
// tools as `unknown`.
_allKnownToolNames = Object.keys(allDefs);
// メタツール: ワークスペースのツール設定に関係なく常に利用可能
// - ReadToolDoc: 全ツールのドキュメント参照
// - CreateChecklist / CheckItem / GetChecklist: 進捗管理 (複数ステップタスクで使用)
// - MissionUpdate: タスクの目標 / 進捗のピン止めメモを更新 (会話が長くなって
// 最初の要件を見失わないため。常時上書き可能、未指定フィールドは保持)
// - ListUserAssets / RunUserScript: ユーザーフォルダのスクリプト探索・実行
// - ReadUserAgents / UpdateUserAgents: per-user AGENTS.md (常時指示書) の読み書き
// (UpdateUserMemory が事実断片、こちらは振る舞い方針そのもの)
// - Brainstorm: 着手前 or 行き詰まり時の多アプローチ比較 (issue #247)
// - ReadAppDoc / ListAppDocs / GetMyOrchestratorState: Help アシスタント用 (#help piece)
// ただし他の piece からも参照できるようにメタ扱い
// - RequestTool: 足りないツールの要求を記録(タスク詳細/ピース集計で可視化)
const META_TOOLS = ['ReadToolDoc', 'CreateChecklist', 'CheckItem', 'GetChecklist', 'MissionUpdate', 'SearchTaskConversation', 'ReadTaskConversation', 'SearchWorkspaceTasks', 'GetFileProvenance', 'ListWorkspaceFiles', 'ListUserAssets', 'RunUserScript', 'UpdateUserMemory', 'ReadUserMemory', 'ReadUserAgents', 'UpdateUserAgents', 'WriteUserScript', 'Brainstorm', 'ReadAppDoc', 'ListAppDocs', 'GetMyOrchestratorState', 'ReadSkill', 'ListSkills', 'InstallSkill', 'RequestTool'];
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]!);
// Per-space isolation: the MCP server set is scoped to the job's space.
// options.spaceId === undefined → aggregator falls back to the legacy
// NULL-space set (non-space jobs keep prior behaviour).
const mcpDefs =
_mcpAggregator && options?.ownerId && !options?.mcpDisabled
? await _mcpAggregator.getToolDefs(options.ownerId, allowedTools, options.spaceId)
: [];
return [...staticDefs, ...mcpDefs];
}
/**
* ツールを実行する内部ルーター。
* core → web → image → data の順で各モジュールに委譲し、
* 最初に null でない結果を返したモジュールの結果を使う。
*/
async function executeToolInner(
name: string,
input: Record<string, unknown>,
ctx: ToolContext,
): Promise<ToolResult> {
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,
// Job cancel/deadline signal so a slow/hung MCP server is interrupted
// instead of pinning the worker slot past the deadline.
abortSignal: ctx.abortSignal,
// Per-space isolation guard (defense in depth). undefined = legacy
// NULL-space job → only NULL-space servers are callable.
spaceId: ctx.spaceId ?? null,
});
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;
}
// 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;
}
// task-conversation ツール (SearchTaskConversation / ReadTaskConversation)
const taskConversationMod = await getTaskConversationModule();
if (taskConversationMod) {
const taskConversationResult = await taskConversationMod.executeTool(name, input, ctx);
if (taskConversationResult !== null) return taskConversationResult;
}
// workspace-task-search ツール (SearchWorkspaceTasks)
const workspaceTaskSearchMod2 = await getWorkspaceTaskSearchModule();
if (workspaceTaskSearchMod2) {
const r = await workspaceTaskSearchMod2.executeTool(name, input, ctx);
if (r !== null) return r;
}
// file-provenance ツール (GetFileProvenance / ListWorkspaceFiles)
const fileProvenanceMod = await getFileProvenanceModule();
if (fileProvenanceMod) {
const fileProvenanceResult = await fileProvenanceMod.executeTool(name, input, ctx);
if (fileProvenanceResult !== null) return fileProvenanceResult;
}
// RequestTool: 足りないツールの要求を記録。分類用に既知ツール一覧を注入する
// (getToolDefs が movement 開始時に更新した最新スナップショット)。
const toolRequestMod = await getToolRequestModule();
if (toolRequestMod) {
if (name === 'RequestTool') ctx.knownToolNames = _allKnownToolNames;
const toolRequestResult = await toolRequestMod.executeTool(name, input, ctx);
if (toolRequestResult !== null) return toolRequestResult;
}
// 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;
}
// calendar ツール (AddCalendarEvent / ListCalendarEvents)
const calendarMod = await getCalendarModule();
if (calendarMod) {
const calendarResult = await calendarMod.executeTool(name, input, ctx);
if (calendarResult !== null) return calendarResult;
}
// app-test ツール (TestWorkspaceApp)
const appTestMod = await getAppTestModule();
if (appTestMod) {
const appTestResult = await appTestMod.executeTool(name, input, ctx);
if (appTestResult !== null) return appTestResult;
}
// 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<string, unknown>,
ctx: ToolContext,
): Promise<ToolResult> {
logger.debug(`[tools/index] executing ${name}`);
const result = await executeToolInner(name, input, ctx);
// raw保存ラッパー: 成功した対象ツールの結果を {logsRoot}/raw/ に保存(計画5: runtimeDir 解決)
if (ctx.workspacePath && !result.isError) {
const logsRoot = runtimeLogsDir(ctx);
if (RAW_SAVE_TOOLS.has(name)) {
saveRawData(logsRoot, name, result.output);
} else if (RAW_LOG_ONLY_TOOLS.has(name)) {
const pathMatch = result.output.match(/-> (.+?) \(/);
if (pathMatch?.[1]) {
logRawDownload(logsRoot, name, pathMatch[1], result.output.length);
}
}
// 構造化データ保存: structuredBlocks があれば {logsRoot}/structured/ に保存
if (result.structuredBlocks?.length) {
saveStructuredBlocks(logsRoot, result.structuredBlocks);
}
}
return result;
}