This commit is contained in:
+74
-58
@@ -7,6 +7,7 @@ import {
|
||||
ALL_TOOL_DEFS,
|
||||
getToolDefs as getCoreToolDefs,
|
||||
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';
|
||||
@@ -58,7 +59,6 @@ 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;
|
||||
@@ -175,14 +175,6 @@ async function getChecklistModule(): Promise<ToolModule | null> {
|
||||
return _checklistModule;
|
||||
}
|
||||
|
||||
async function getKnowledgeModule(): Promise<ToolModule | null> {
|
||||
if (_knowledgeModule === undefined) {
|
||||
_knowledgeModule = await tryLoadModule('./knowledge.js');
|
||||
if (_knowledgeModule) logger.debug('[tools/index] knowledge module loaded');
|
||||
}
|
||||
return _knowledgeModule;
|
||||
}
|
||||
|
||||
async function getSlideModule(): Promise<ToolModule | null> {
|
||||
if (_slideModule === undefined) {
|
||||
_slideModule = await tryLoadModule('./slide.js');
|
||||
@@ -217,6 +209,24 @@ async function getMissionModule(): Promise<ToolModule | null> {
|
||||
return _missionModule;
|
||||
}
|
||||
|
||||
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');
|
||||
@@ -261,22 +271,13 @@ async function getSshConsoleModule(): Promise<ToolModule | null> {
|
||||
return _sshConsoleModule;
|
||||
}
|
||||
|
||||
let _notesModule: ToolModule | null | undefined;
|
||||
async function getNotesModule(): Promise<ToolModule | null> {
|
||||
if (_notesModule === undefined) {
|
||||
_notesModule = await tryLoadModule('./notes.js');
|
||||
if (_notesModule) logger.debug('[tools/index] notes module loaded');
|
||||
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 _notesModule;
|
||||
}
|
||||
|
||||
let _dashboardModule: ToolModule | null | undefined = undefined;
|
||||
async function getDashboardModule(): Promise<ToolModule | null> {
|
||||
if (_dashboardModule === undefined) {
|
||||
_dashboardModule = await tryLoadModule('./dashboard.js');
|
||||
if (_dashboardModule) logger.debug('[tools/index] dashboard module loaded');
|
||||
}
|
||||
return _dashboardModule;
|
||||
return _calendarModule;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,7 +287,7 @@ async function getDashboardModule(): Promise<ToolModule | null> {
|
||||
export async function getToolDefs(
|
||||
allowedTools: string[],
|
||||
editAllowed: boolean,
|
||||
options?: { vlmEnabled?: boolean; ownerId?: string | null; mcpDisabled?: boolean },
|
||||
options?: { vlmEnabled?: boolean; ownerId?: string | null; mcpDisabled?: boolean; spaceId?: string | null },
|
||||
): Promise<ToolDef[]> {
|
||||
// 全ツール定義を収集
|
||||
const allDefs: Record<string, ToolDef> = { ...ALL_TOOL_DEFS };
|
||||
@@ -333,9 +334,6 @@ export async function getToolDefs(
|
||||
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);
|
||||
|
||||
@@ -363,15 +361,30 @@ export async function getToolDefs(
|
||||
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 calendarMod = await getCalendarModule();
|
||||
if (calendarMod) Object.assign(allDefs, calendarMod.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);
|
||||
|
||||
// メタツール: piece の allowed_tools に書かれていなくても常に利用可能
|
||||
// - ReadToolDoc: 全ツールのドキュメント参照
|
||||
// - CreateChecklist / CheckItem / GetChecklist: 進捗管理 (複数ステップタスクで使用)
|
||||
@@ -383,7 +396,8 @@ export async function getToolDefs(
|
||||
// - Brainstorm: 着手前 or 行き詰まり時の多アプローチ比較 (issue #247)
|
||||
// - ReadAppDoc / ListAppDocs / GetMyOrchestratorState: Help アシスタント用 (#help piece)
|
||||
// ただし他の piece からも参照できるようにメタ扱い
|
||||
const META_TOOLS = ['ReadToolDoc', 'CreateChecklist', 'CheckItem', 'GetChecklist', 'MissionUpdate', 'ListUserAssets', 'RunUserScript', 'UpdateUserMemory', 'ReadUserMemory', 'ReadUserAgents', 'UpdateUserAgents', 'ReadUserTemplate', 'RenderUserTemplate', 'WriteUserScript', 'WriteUserTemplate', 'Brainstorm', 'ReadAppDoc', 'ListAppDocs', 'GetMyOrchestratorState', 'ReadSkill', 'ListSkills', 'InstallSkill'];
|
||||
// - RequestTool: 足りないツールの要求を記録(タスク詳細/ピース集計で可視化)
|
||||
const META_TOOLS = ['ReadToolDoc', 'CreateChecklist', 'CheckItem', 'GetChecklist', 'MissionUpdate', 'ListUserAssets', 'RunUserScript', 'UpdateUserMemory', 'ReadUserMemory', 'ReadUserAgents', 'UpdateUserAgents', 'ReadUserTemplate', 'RenderUserTemplate', 'WriteUserScript', 'WriteUserTemplate', 'Brainstorm', 'ReadAppDoc', 'ListAppDocs', 'GetMyOrchestratorState', 'ReadSkill', 'ListSkills', 'InstallSkill', 'RequestTool'];
|
||||
const effectiveAllowed = [...allowedTools];
|
||||
for (const meta of META_TOOLS) {
|
||||
if (!effectiveAllowed.includes(meta) && meta in allDefs) {
|
||||
@@ -399,9 +413,12 @@ export async function getToolDefs(
|
||||
})
|
||||
.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)
|
||||
? await _mcpAggregator.getToolDefs(options.ownerId, allowedTools, options.spaceId)
|
||||
: [];
|
||||
|
||||
return [...staticDefs, ...mcpDefs];
|
||||
@@ -437,6 +454,9 @@ async function executeToolInner(
|
||||
jobId: ctxWithMcp.jobId ?? '',
|
||||
config: ctxWithMcp.mcpConfig,
|
||||
quotaState: ctxWithMcp.mcpQuotaState,
|
||||
// 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 };
|
||||
@@ -546,13 +566,6 @@ async function executeToolInner(
|
||||
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) {
|
||||
@@ -581,6 +594,15 @@ async function executeToolInner(
|
||||
if (missionResult !== null) return missionResult;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -616,18 +638,11 @@ async function executeToolInner(
|
||||
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;
|
||||
// calendar ツール (AddCalendarEvent / ListCalendarEvents)
|
||||
const calendarMod = await getCalendarModule();
|
||||
if (calendarMod) {
|
||||
const calendarResult = await calendarMod.executeTool(name, input, ctx);
|
||||
if (calendarResult !== null) return calendarResult;
|
||||
}
|
||||
|
||||
// skills ツール (ReadSkill)
|
||||
@@ -651,19 +666,20 @@ export async function executeTool(
|
||||
|
||||
const result = await executeToolInner(name, input, ctx);
|
||||
|
||||
// raw保存ラッパー: 成功した対象ツールの結果を logs/raw/ に保存
|
||||
// raw保存ラッパー: 成功した対象ツールの結果を {logsRoot}/raw/ に保存(計画5: runtimeDir 解決)
|
||||
if (ctx.workspacePath && !result.isError) {
|
||||
const logsRoot = runtimeLogsDir(ctx);
|
||||
if (RAW_SAVE_TOOLS.has(name)) {
|
||||
saveRawData(ctx.workspacePath, name, result.output);
|
||||
saveRawData(logsRoot, 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);
|
||||
logRawDownload(logsRoot, name, pathMatch[1], result.output.length);
|
||||
}
|
||||
}
|
||||
// 構造化データ保存: structuredBlocks があれば logs/structured/ に保存
|
||||
// 構造化データ保存: structuredBlocks があれば {logsRoot}/structured/ に保存
|
||||
if (result.structuredBlocks?.length) {
|
||||
saveStructuredBlocks(ctx.workspacePath, result.structuredBlocks);
|
||||
saveStructuredBlocks(logsRoot, result.structuredBlocks);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user