This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* RequestTool — META_TOOL for the tool-request mechanism.
|
||||
*
|
||||
* When an agent realises it needs a tool that this movement does not expose,
|
||||
* it calls RequestTool({ name, reason }). The request is recorded so the gap
|
||||
* becomes visible in the task detail and per-piece aggregation, letting the
|
||||
* piece author add the tool to `allowed_tools` / `shared_tools`. This PR
|
||||
* (recording infra) does NOT grant the tool — it records and guides. The
|
||||
* inline approval/grant flow is layered on top in a later PR.
|
||||
*
|
||||
* Classification (recorded as `category`):
|
||||
* - already_available → the tool IS exposed here; just call it (not recorded).
|
||||
* - available_not_allowed (`requested`) → exists in the catalog but not in
|
||||
* this movement → recorded as a genuine omission candidate.
|
||||
* - unknown → not in the catalog (possible hallucination / capability gap).
|
||||
*
|
||||
* Plumbing: ToolContext carries `effectiveToolNames` (this movement's set),
|
||||
* `knownToolNames` (full catalog, injected by the dispatcher), and
|
||||
* `recordToolRequest` (bound to task/job/space by the worker).
|
||||
*/
|
||||
|
||||
import { ToolDef } from '../../llm/openai-compat.js';
|
||||
import type { ToolContext, ToolResult } from './core.js';
|
||||
|
||||
const REQUEST_TOOL_DEF: ToolDef = {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'RequestTool',
|
||||
description:
|
||||
'この movement に無いツールがどうしても必要なときに要求する。name と理由(reason)を渡すと記録され、ピース作者が allowed_tools / shared_tools に追加できる。要求しても今すぐ使えるようにはならない — 無しで進めるか、無理なら complete({status:"needs_user_input"}) でユーザーに依頼すること。詳細は ReadToolDoc({ name: "RequestTool" })。',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: '必要なツール名(例: WebSearch, Bash)。' },
|
||||
reason: { type: 'string', description: 'なぜそのツールが必要か(具体的に)。' },
|
||||
},
|
||||
required: ['name', 'reason'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const TOOL_DEFS: Record<string, ToolDef> = {
|
||||
RequestTool: REQUEST_TOOL_DEF,
|
||||
};
|
||||
|
||||
export async function executeTool(
|
||||
name: string,
|
||||
input: Record<string, unknown>,
|
||||
ctx: ToolContext,
|
||||
): Promise<ToolResult | null> {
|
||||
if (name !== 'RequestTool') return null;
|
||||
|
||||
const toolName = typeof input['name'] === 'string' ? input['name'].trim() : '';
|
||||
const reason = typeof input['reason'] === 'string' ? input['reason'].trim() : '';
|
||||
if (!toolName) return { output: 'RequestTool: name は必須です。', isError: true };
|
||||
if (!reason) return { output: 'RequestTool: reason(なぜ必要か)は必須です。', isError: true };
|
||||
|
||||
// Already available in THIS movement → nothing to record, just tell it to call.
|
||||
const effective = ctx.effectiveToolNames ?? [];
|
||||
if (effective.includes(toolName)) {
|
||||
return {
|
||||
output: `"${toolName}" はこの movement で既に利用可能です。そのまま呼び出してください。`,
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Classify against the full catalog. MCP tools (mcp__*) can't be cheaply
|
||||
// enumerated, so treat them as "exists but not allowed here".
|
||||
const known = ctx.knownToolNames ?? [];
|
||||
const isMcp = toolName.startsWith('mcp__');
|
||||
const category: 'requested' | 'unknown' = known.includes(toolName) || isMcp ? 'requested' : 'unknown';
|
||||
|
||||
if (category === 'unknown') {
|
||||
ctx.recordToolRequest?.({ toolName, reason, category: 'unknown' });
|
||||
return {
|
||||
output:
|
||||
`"${toolName}" というツールは存在しません(要求は記録しました)。利用可能なツールで進めてください。` +
|
||||
` 何が使えるかは ReadToolDoc などで確認できます。`,
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
|
||||
// available_not_allowed. If this run can reach a user, pause for inline
|
||||
// approval (blocking permission-prompt). Otherwise record auto_denied and
|
||||
// let the agent proceed without it (the user finds the request later).
|
||||
if (ctx.toolApprovalInteractive) {
|
||||
ctx.recordToolRequest?.({ toolName, reason, category: 'requested', status: 'pending' });
|
||||
ctx.pendingToolApproval = { toolName };
|
||||
return {
|
||||
output:
|
||||
`"${toolName}" の利用にはユーザーの承認が必要です。承認/拒否を待っています。` +
|
||||
` 承認されればこのまま続行でき、拒否されればそのツール無しで進めることになります。`,
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
ctx.recordToolRequest?.({ toolName, reason, category: 'requested', status: 'auto_denied' });
|
||||
return {
|
||||
output:
|
||||
`"${toolName}" はこの movement では許可されていないため使えません(要求を記録しました)。` +
|
||||
` このツール無しで進めるか、達成できない場合は complete({status:"needs_user_input", missing_info:"..."}) でユーザーに依頼してください。` +
|
||||
` ピースの allowed_tools / shared_tools に追加すると次回から使えます。`,
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user