mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-12 23:01:21 +00:00
schema trigger implemntation done
This commit is contained in:
parent
04965a7cf0
commit
e226d1e2a5
11 changed files with 139 additions and 12 deletions
|
|
@ -468,6 +468,12 @@ export class NativeToolCallParser {
|
|||
nativeArgs = {
|
||||
path: partialArgs.path,
|
||||
content: partialArgs.content,
|
||||
intent_id: partialArgs.intent_id,
|
||||
mutation_class: partialArgs.mutation_class as
|
||||
| "AST_REFACTOR"
|
||||
| "INTENT_EVOLUTION"
|
||||
| "NEW_FILE"
|
||||
| undefined,
|
||||
}
|
||||
}
|
||||
break
|
||||
|
|
@ -905,6 +911,12 @@ export class NativeToolCallParser {
|
|||
nativeArgs = {
|
||||
path: args.path,
|
||||
content: args.content,
|
||||
intent_id: args.intent_id,
|
||||
mutation_class: args.mutation_class as
|
||||
| "AST_REFACTOR"
|
||||
| "INTENT_EVOLUTION"
|
||||
| "NEW_FILE"
|
||||
| undefined,
|
||||
} as NativeArgsFor<TName>
|
||||
}
|
||||
break
|
||||
|
|
|
|||
|
|
@ -676,18 +676,48 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
}
|
||||
}
|
||||
|
||||
// Hook Engine: Post-Hook for write_to_file appends to agent_trace.jsonl
|
||||
const preHook = new PreHook({
|
||||
cwd: cline.cwd,
|
||||
getActiveIntentId: () => cline.getActiveIntentId(),
|
||||
setActiveIntentId: (id) => cline.setActiveIntentId(id),
|
||||
requireIntentForDestructiveOnly: true,
|
||||
})
|
||||
const hookMiddleware = new HookMiddleware({
|
||||
preHook,
|
||||
getActiveIntentId: () => cline.getActiveIntentId(),
|
||||
getCwd: () => cline.cwd,
|
||||
getReqId: () => cline.taskId,
|
||||
getSessionLogId: () => undefined,
|
||||
getModelId: () => cline.api.getModel()?.id,
|
||||
getVcsRevisionId: () => undefined,
|
||||
})
|
||||
|
||||
switch (block.name) {
|
||||
case "select_active_intent":
|
||||
// Handled entirely by pre-hook (injectResult pushed above)
|
||||
break
|
||||
case "write_to_file":
|
||||
case "write_to_file": {
|
||||
await checkpointSaveAndMark(cline)
|
||||
await writeToFileTool.handle(cline, block as ToolUse<"write_to_file">, {
|
||||
askApproval,
|
||||
handleError,
|
||||
pushToolResult,
|
||||
onWriteToFileSuccess: async (p) => {
|
||||
await hookMiddleware.postToolUse(
|
||||
"write_to_file",
|
||||
{
|
||||
path: p.path,
|
||||
content: p.content,
|
||||
intent_id: p.intent_id,
|
||||
mutation_class: p.mutation_class,
|
||||
},
|
||||
{},
|
||||
)
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
case "update_todo_list":
|
||||
await updateTodoListTool.handle(cline, block as ToolUse<"update_todo_list">, {
|
||||
askApproval,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import type OpenAI from "openai"
|
||||
|
||||
const MUTATION_CLASS_ENUM = ["AST_REFACTOR", "INTENT_EVOLUTION", "NEW_FILE"] as const
|
||||
|
||||
const WRITE_TO_FILE_DESCRIPTION = `Request to write content to a file. This tool is primarily used for creating new files or for scenarios where a complete rewrite of an existing file is intentionally required. If the file exists, it will be overwritten. If it doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
||||
|
||||
**Important:** You should prefer using other editing tools over write_to_file when making changes to existing files, since write_to_file is slower and cannot handle large files. Use write_to_file primarily for new file creation.
|
||||
|
|
@ -8,13 +10,22 @@ When using this tool, use it directly with the desired content. You do not need
|
|||
|
||||
When creating a new project, organize all new files within a dedicated project directory unless the user specifies otherwise. Structure the project logically, adhering to best practices for the specific type of project being created.
|
||||
|
||||
**Traceability:** You MUST provide intent_id (the active intent from select_active_intent) and mutation_class:
|
||||
- AST_REFACTOR: Syntax/structural change, same intent (e.g. rename, format, extract function).
|
||||
- INTENT_EVOLUTION: New feature or behavior change tied to the intent.
|
||||
- NEW_FILE: Creating a file that did not exist before.
|
||||
|
||||
Example: Writing a configuration file
|
||||
{ "path": "frontend-config.json", "content": "{\\n \\"apiEndpoint\\": \\"https://api.example.com\\",\\n \\"theme\\": {\\n \\"primaryColor\\": \\"#007bff\\"\\n }\\n}" }`
|
||||
{ "path": "frontend-config.json", "content": "{\\n \\"apiEndpoint\\": \\"https://api.example.com\\",\\n \\"theme\\": {\\n \\"primaryColor\\": \\"#007bff\\"\\n }\\n}", "intent_id": "INT-001", "mutation_class": "INTENT_EVOLUTION" }`
|
||||
|
||||
const PATH_PARAMETER_DESCRIPTION = `The path of the file to write to (relative to the current workspace directory)`
|
||||
|
||||
const CONTENT_PARAMETER_DESCRIPTION = `The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include line numbers in the content.`
|
||||
|
||||
const INTENT_ID_DESCRIPTION = `The ID of the active intent (from select_active_intent) that this write serves. Required for traceability.`
|
||||
|
||||
const MUTATION_CLASS_DESCRIPTION = `Semantic classification: AST_REFACTOR (syntax change, same intent), INTENT_EVOLUTION (new feature/behavior), or NEW_FILE (creating a new file).`
|
||||
|
||||
export default {
|
||||
type: "function",
|
||||
function: {
|
||||
|
|
@ -32,8 +43,17 @@ export default {
|
|||
type: "string",
|
||||
description: CONTENT_PARAMETER_DESCRIPTION,
|
||||
},
|
||||
intent_id: {
|
||||
type: "string",
|
||||
description: INTENT_ID_DESCRIPTION,
|
||||
},
|
||||
mutation_class: {
|
||||
type: "string",
|
||||
enum: MUTATION_CLASS_ENUM,
|
||||
description: MUTATION_CLASS_DESCRIPTION,
|
||||
},
|
||||
},
|
||||
required: ["path", "content"],
|
||||
required: ["path", "content", "intent_id", "mutation_class"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import type { ToolUse, HandleError, PushToolResult, AskApproval, NativeToolArgs
|
|||
export interface WriteToFileSuccessParams {
|
||||
path: string
|
||||
content: string
|
||||
intent_id?: string
|
||||
mutation_class?: string
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -180,9 +180,11 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> {
|
|||
pushToolResult(message)
|
||||
|
||||
const mutationClass = (params as Record<string, unknown>).mutation_class as string | undefined
|
||||
const intentId = (params as Record<string, unknown>).intent_id as string | undefined
|
||||
await callbacks.onWriteToFileSuccess?.({
|
||||
path: relPath,
|
||||
content: newContent,
|
||||
intent_id: intentId,
|
||||
mutation_class: mutationClass,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,23 @@ import crypto from "crypto"
|
|||
const HASH_PREFIX = "sha256:"
|
||||
|
||||
/**
|
||||
* Compute a SHA-256 content hash for spatial independence.
|
||||
* If lines move, the hash of the content block remains valid.
|
||||
* Generate a SHA-256 hash of string content (spatial hashing utility).
|
||||
* Returns a prefixed hex string (e.g. "sha256:abc123...") for traceability.
|
||||
* Content hash remains valid even if line positions change.
|
||||
*/
|
||||
export function contentHash(content: string): string {
|
||||
const hash = crypto.createHash("sha256").update(content, "utf8").digest("hex")
|
||||
return `${HASH_PREFIX}${hash}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for contentHash - generates SHA-256 hash of string content.
|
||||
* Use for spatial hashing in agent trace and diff operations.
|
||||
*/
|
||||
export function sha256Hash(content: string): string {
|
||||
return contentHash(content)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a logical code block (e.g. by line range) and return its hash.
|
||||
*/
|
||||
|
|
|
|||
31
src/hooks/format.ts
Normal file
31
src/hooks/format.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* Minimal tool error formatters for pre-hook.
|
||||
* Avoids importing from core/prompts/responses to prevent vscode dependency in Node scripts.
|
||||
*/
|
||||
export const toolErrorFormat = {
|
||||
toolError: (error?: string) =>
|
||||
JSON.stringify({
|
||||
status: "error",
|
||||
message: "The tool execution failed",
|
||||
error,
|
||||
}),
|
||||
|
||||
toolErrorScopeViolation: (intentId: string, filename: string) =>
|
||||
JSON.stringify({
|
||||
status: "error",
|
||||
type: "scope_violation",
|
||||
message: `Scope Violation: ${intentId} is not authorized to edit [${filename}]. Request scope expansion.`,
|
||||
intent_id: intentId,
|
||||
path: filename,
|
||||
suggestion: "Request scope expansion in .orchestration/active_intents.yaml or choose another intent.",
|
||||
}),
|
||||
|
||||
toolErrorUserRejected: (toolName?: string) =>
|
||||
JSON.stringify({
|
||||
status: "error",
|
||||
type: "user_rejected",
|
||||
message: "The user rejected this operation.",
|
||||
tool: toolName,
|
||||
suggestion: "Do not retry the same operation; try a different approach or ask the user for permission.",
|
||||
}),
|
||||
}
|
||||
|
|
@ -6,6 +6,8 @@ export interface HookMiddlewareOptions {
|
|||
preHook: PreHook
|
||||
getActiveIntentId: () => string | null
|
||||
getCwd: () => string
|
||||
/** REQ-ID from Phase 1 - injected into agent_trace related array */
|
||||
getReqId?: () => string | undefined
|
||||
getSessionLogId?: () => string | undefined
|
||||
getModelId?: () => string | undefined
|
||||
getVcsRevisionId?: () => string | undefined
|
||||
|
|
@ -39,7 +41,8 @@ export class HookMiddleware {
|
|||
const contentParam = params.content
|
||||
if (typeof pathParam !== "string" || typeof contentParam !== "string") return
|
||||
|
||||
const intentId = this.options.getActiveIntentId()
|
||||
const intentId =
|
||||
(typeof params.intent_id === "string" ? params.intent_id.trim() : null) || this.options.getActiveIntentId()
|
||||
const mutationClass = (params.mutation_class as "AST_REFACTOR" | "INTENT_EVOLUTION" | "NEW_FILE") ?? "UNKNOWN"
|
||||
|
||||
await appendAgentTrace(this.options.getCwd(), {
|
||||
|
|
@ -47,6 +50,7 @@ export class HookMiddleware {
|
|||
content: contentParam,
|
||||
intentId,
|
||||
mutationClass,
|
||||
reqId: this.options.getReqId?.(),
|
||||
sessionLogId: this.options.getSessionLogId?.(),
|
||||
modelIdentifier: this.options.getModelId?.(),
|
||||
vcsRevisionId: this.options.getVcsRevisionId?.(),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ export interface PostHookWriteParams {
|
|||
content: string
|
||||
intentId: string | null
|
||||
mutationClass?: MutationClass
|
||||
/** REQ-ID from Phase 1 - injected into related array for traceability */
|
||||
reqId?: string
|
||||
sessionLogId?: string
|
||||
modelIdentifier?: string
|
||||
vcsRevisionId?: string
|
||||
|
|
@ -28,6 +30,7 @@ export async function appendAgentTrace(cwd: string, params: PostHookWriteParams)
|
|||
content,
|
||||
intentId,
|
||||
mutationClass = "UNKNOWN",
|
||||
reqId,
|
||||
sessionLogId,
|
||||
modelIdentifier = "unknown",
|
||||
vcsRevisionId,
|
||||
|
|
@ -39,11 +42,15 @@ export async function appendAgentTrace(cwd: string, params: PostHookWriteParams)
|
|||
|
||||
const lines = content.split("\n")
|
||||
const fullRangeHash = contentHash(content)
|
||||
const related: Array<{ type: string; value: string }> = []
|
||||
if (intentId) related.push({ type: "specification", value: intentId })
|
||||
if (reqId) related.push({ type: "request", value: reqId })
|
||||
|
||||
const conversation: AgentTraceConversation = {
|
||||
url: sessionLogId,
|
||||
contributor: { entity_type: "AI", model_identifier: modelIdentifier },
|
||||
ranges: [{ start_line: 1, end_line: lines.length, content_hash: fullRangeHash }],
|
||||
related: intentId ? [{ type: "specification", value: intentId }] : [],
|
||||
related,
|
||||
}
|
||||
|
||||
const fileEntry: AgentTraceFileEntry = {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ import path from "path"
|
|||
import type { HookResult } from "./types"
|
||||
import { DESTRUCTIVE_TOOLS } from "./types"
|
||||
import { loadIntentContext, buildConsolidatedIntentContextXml } from "./context-loader"
|
||||
import { loadIntentIgnore, isPathIgnored, isIntentExcluded } from "./intent-ignore"
|
||||
import type { IntentIgnoreResult } from "./intent-ignore"
|
||||
import { pathInScope } from "./scope"
|
||||
import { toolErrorFormat } from "./format"
|
||||
|
||||
/** Block paths that escape workspace (.. or absolute outside cwd). */
|
||||
function isPathTraversal(relPath: string, cwd: string): boolean {
|
||||
|
|
@ -76,7 +79,7 @@ export class PreHook {
|
|||
if (isIntentExcluded(activeId, ignore.excludedIntentIds)) {
|
||||
return {
|
||||
blocked: true,
|
||||
error: formatResponse.toolError(
|
||||
error: toolErrorFormat.toolError(
|
||||
`Intent ${activeId} is listed in .intentignore and cannot be modified. Choose another intent or ask the user to update .intentignore.`,
|
||||
),
|
||||
}
|
||||
|
|
@ -107,7 +110,7 @@ export class PreHook {
|
|||
if (isPathIgnored(relPath, ignore.pathPatterns)) {
|
||||
return {
|
||||
blocked: true,
|
||||
error: formatResponse.toolError(
|
||||
error: toolErrorFormat.toolError(
|
||||
`Path "${relPath}" is excluded by .intentignore. You are not authorized to edit it.`,
|
||||
),
|
||||
}
|
||||
|
|
@ -116,7 +119,7 @@ export class PreHook {
|
|||
if (context && context.owned_scope.length > 0 && !pathInScope(relPath, context.owned_scope, cwd)) {
|
||||
return {
|
||||
blocked: true,
|
||||
error: formatResponse.toolErrorScopeViolation(activeId, relPath),
|
||||
error: toolErrorFormat.toolErrorScopeViolation(activeId, relPath),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -128,7 +131,7 @@ export class PreHook {
|
|||
if (!approved) {
|
||||
return {
|
||||
blocked: true,
|
||||
error: formatResponse.toolErrorUserRejected(toolName),
|
||||
error: toolErrorFormat.toolErrorUserRejected(toolName),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@ export const toolParamNames = [
|
|||
// read_file legacy format parameter (backward compatibility)
|
||||
"files",
|
||||
"line_ranges",
|
||||
// write_to_file traceability (AI-Native Git Layer)
|
||||
"intent_id",
|
||||
"mutation_class",
|
||||
] as const
|
||||
|
||||
export type ToolParamName = (typeof toolParamNames)[number]
|
||||
|
|
@ -114,7 +117,12 @@ export type NativeToolArgs = {
|
|||
switch_mode: { mode_slug: string; reason: string }
|
||||
update_todo_list: { todos: string }
|
||||
use_mcp_tool: { server_name: string; tool_name: string; arguments?: Record<string, unknown> }
|
||||
write_to_file: { path: string; content: string }
|
||||
write_to_file: {
|
||||
path: string
|
||||
content: string
|
||||
intent_id?: string
|
||||
mutation_class?: "AST_REFACTOR" | "INTENT_EVOLUTION" | "NEW_FILE"
|
||||
}
|
||||
// Add more tools as they are migrated to native protocol
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue