766 lines
29 KiB
TypeScript
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;
|
|
}
|