@kolisachint/hoocode-agent-core 0.5.49 → 0.5.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Headless default tool bundle: the same built-in tools the hoocode CLI
3
- * registers (bash/read/edit/write/grep/find/ls), implemented without any
4
- * CLI or TUI dependency so they can run in a separate process (for example
5
- * a hooteams worker). The CLI keeps its own richer implementations with
3
+ * registers (bash/read/edit/write), implemented without any CLI or TUI
4
+ * dependency so they can run in a separate process (for example a hooteams
5
+ * worker). The CLI keeps its own richer implementations with
6
6
  * interactive rendering; these share the tool names and parameter contracts.
7
7
  *
8
8
  * No singletons, no top-level side effects: every call to getDefaultTools()
@@ -13,11 +13,9 @@ export interface DefaultToolsOptions {
13
13
  /** Working directory the tools operate in. Defaults to process.cwd(). */
14
14
  cwd?: string;
15
15
  }
16
- /** Convert a glob pattern to a regular expression over `/`-separated paths. */
17
- export declare function globToRegExp(pattern: string): RegExp;
18
16
  /**
19
- * Build the default headless tool bundle (bash/read/edit/write/grep/find/ls)
20
- * bound to the given working directory.
17
+ * Build the default headless tool bundle (bash/read/edit/write) bound to the
18
+ * given working directory.
21
19
  *
22
20
  * The CLI's Task tool is intentionally not part of this bundle: it requires
23
21
  * the CLI's subagent runtime (agent registry, subagent pool, session
@@ -1 +1 @@
1
- {"version":3,"file":"default-tools.d.ts","sourceRoot":"","sources":["../../src/tools/default-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAgBH,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,aAAa,CAAC;AAE9D,MAAM,WAAW,mBAAmB;IACnC,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAwMD,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA0BpD;AA+ND;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAY5E","sourcesContent":["/**\n * Headless default tool bundle: the same built-in tools the hoocode CLI\n * registers (bash/read/edit/write/grep/find/ls), implemented without any\n * CLI or TUI dependency so they can run in a separate process (for example\n * a hooteams worker). The CLI keeps its own richer implementations with\n * interactive rendering; these share the tool names and parameter contracts.\n *\n * No singletons, no top-level side effects: every call to getDefaultTools()\n * builds a fresh bundle bound to the given cwd.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { isAbsolute, join, relative, resolve } from \"node:path\";\nimport ignore from \"ignore\";\nimport { type Static, Type } from \"typebox\";\nimport { NodeExecutionEnv } from \"../harness/env/nodejs.js\";\nimport { FileError } from \"../harness/types.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttruncateHead,\n\ttruncateLine,\n\ttruncateTail,\n} from \"../harness/utils/truncate.js\";\nimport type { AgentTool, AgentToolResult } from \"../types.js\";\n\nexport interface DefaultToolsOptions {\n\t/** Working directory the tools operate in. Defaults to process.cwd(). */\n\tcwd?: string;\n}\n\nfunction textResult(text: string): AgentToolResult<undefined> {\n\treturn { content: [{ type: \"text\", text }], details: undefined };\n}\n\nfunction resolveToCwd(cwd: string, path: string): string {\n\treturn isAbsolute(path) ? path : resolve(cwd, path);\n}\n\n// ---------------------------------------------------------------------------\n// bash\n// ---------------------------------------------------------------------------\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\nfunction createBashTool(env: NodeExecutionEnv): AgentTool<typeof bashSchema> {\n\treturn {\n\t\tname: \"bash\",\n\t\tlabel: \"bash\",\n\t\tdescription: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Optionally provide a timeout in seconds.`,\n\t\tparameters: bashSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof bashSchema>, signal) => {\n\t\t\tlet combined = \"\";\n\t\t\tlet exitCode: number;\n\t\t\ttry {\n\t\t\t\tconst result = await env.exec(params.command, {\n\t\t\t\t\ttimeout: params.timeout,\n\t\t\t\t\tsignal,\n\t\t\t\t\tonStdout: (chunk) => {\n\t\t\t\t\t\tcombined += chunk;\n\t\t\t\t\t},\n\t\t\t\t\tonStderr: (chunk) => {\n\t\t\t\t\t\tcombined += chunk;\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\texitCode = result.exitCode;\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tif (message.startsWith(\"timeout:\")) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Command timed out after ${params.timeout}s${combined ? `\\nOutput so far:\\n${combined}` : \"\"}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tconst truncation = truncateTail(combined);\n\t\t\tlet text = truncation.content;\n\t\t\tif (truncation.truncated) {\n\t\t\t\ttext = `[Output truncated: showing last ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize(truncation.totalBytes)})]\\n${text}`;\n\t\t\t}\n\t\t\tif (exitCode !== 0) {\n\t\t\t\ttext = text.length > 0 ? `${text}\\nExit code: ${exitCode}` : `Exit code: ${exitCode}`;\n\t\t\t}\n\t\t\treturn textResult(text.length > 0 ? text : \"(no output)\");\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// read\n// ---------------------------------------------------------------------------\n\nconst readSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nfunction createReadTool(env: NodeExecutionEnv): AgentTool<typeof readSchema> {\n\treturn {\n\t\tname: \"read\",\n\t\tlabel: \"read\",\n\t\tdescription: `Read the contents of a text file. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,\n\t\tparameters: readSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof readSchema>) => {\n\t\t\tconst content = await env.readTextFile(params.path);\n\t\t\tlet lines = content.split(\"\\n\");\n\t\t\tconst totalLines = lines.length;\n\t\t\tconst offset = params.offset !== undefined ? Math.max(1, Math.floor(params.offset)) : 1;\n\t\t\tif (offset > totalLines) {\n\t\t\t\tthrow new Error(`Offset ${offset} is past the end of the file (${totalLines} lines)`);\n\t\t\t}\n\t\t\tlines = lines.slice(offset - 1);\n\t\t\tif (params.limit !== undefined) {\n\t\t\t\tlines = lines.slice(0, Math.max(0, Math.floor(params.limit)));\n\t\t\t}\n\t\t\tconst truncation = truncateHead(lines.join(\"\\n\"));\n\t\t\tlet text = truncation.content;\n\t\t\tif (truncation.truncated) {\n\t\t\t\tconst lastShown = offset - 1 + truncation.outputLines;\n\t\t\t\ttext = `${text}\\n[Truncated: showing lines ${offset}-${lastShown} of ${totalLines}. Continue with offset=${lastShown + 1}]`;\n\t\t\t}\n\t\t\treturn textResult(text);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// edit\n// ---------------------------------------------------------------------------\n\nconst replaceEditSchema = Type.Object(\n\t{\n\t\toldText: Type.String({\n\t\t\tdescription:\n\t\t\t\t\"Exact text for one targeted replacement. It must be unique in the original file and must not overlap with any other edits[].oldText in the same call.\",\n\t\t}),\n\t\tnewText: Type.String({ description: \"Replacement text for this targeted edit.\" }),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst editSchema = Type.Object(\n\t{\n\t\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\t\tedits: Type.Array(replaceEditSchema, {\n\t\t\tdescription:\n\t\t\t\t\"One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits.\",\n\t\t}),\n\t},\n\t{ additionalProperties: false },\n);\n\nfunction countOccurrences(haystack: string, needle: string): number {\n\tif (needle.length === 0) return 0;\n\tlet count = 0;\n\tlet index = haystack.indexOf(needle);\n\twhile (index !== -1) {\n\t\tcount++;\n\t\tindex = haystack.indexOf(needle, index + needle.length);\n\t}\n\treturn count;\n}\n\nfunction createEditTool(env: NodeExecutionEnv): AgentTool<typeof editSchema> {\n\treturn {\n\t\tname: \"edit\",\n\t\tlabel: \"edit\",\n\t\tdescription:\n\t\t\t\"Edit a file by replacing exact text. Each edit's oldText must appear exactly once in the file. Provide multiple edits to make several targeted replacements in one call.\",\n\t\tparameters: editSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof editSchema>) => {\n\t\t\tconst original = await env.readTextFile(params.path);\n\t\t\tif (params.edits.length === 0) {\n\t\t\t\tthrow new Error(\"No edits provided\");\n\t\t\t}\n\t\t\tlet content = original;\n\t\t\tfor (const [index, edit] of params.edits.entries()) {\n\t\t\t\tconst occurrences = countOccurrences(original, edit.oldText);\n\t\t\t\tif (occurrences === 0) {\n\t\t\t\t\tthrow new Error(`edits[${index}].oldText not found in ${params.path}`);\n\t\t\t\t}\n\t\t\t\tif (occurrences > 1) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`edits[${index}].oldText matches ${occurrences} locations in ${params.path}; add surrounding context to make it unique`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (!content.includes(edit.oldText)) {\n\t\t\t\t\tthrow new Error(`edits[${index}].oldText overlaps with an earlier edit in the same call`);\n\t\t\t\t}\n\t\t\t\tcontent = content.replace(edit.oldText, edit.newText);\n\t\t\t}\n\t\t\tawait env.writeFile(params.path, content);\n\t\t\treturn textResult(\n\t\t\t\t`Applied ${params.edits.length} edit${params.edits.length === 1 ? \"\" : \"s\"} to ${params.path}`,\n\t\t\t);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// write\n// ---------------------------------------------------------------------------\n\nconst writeSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n\tcontent: Type.String({ description: \"Content to write to the file\" }),\n});\n\nfunction createWriteTool(env: NodeExecutionEnv): AgentTool<typeof writeSchema> {\n\treturn {\n\t\tname: \"write\",\n\t\tlabel: \"write\",\n\t\tdescription: \"Write content to a file, creating parent directories as needed. Overwrites existing files.\",\n\t\tparameters: writeSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof writeSchema>) => {\n\t\t\tawait env.writeFile(params.path, params.content);\n\t\t\treturn textResult(`Wrote ${formatSize(Buffer.byteLength(params.content, \"utf-8\"))} to ${params.path}`);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// shared file walking for grep/find\n// ---------------------------------------------------------------------------\n\n/** Convert a glob pattern to a regular expression over `/`-separated paths. */\nexport function globToRegExp(pattern: string): RegExp {\n\tlet regex = \"\";\n\tfor (let i = 0; i < pattern.length; i++) {\n\t\tconst char = pattern[i];\n\t\tif (char === \"*\") {\n\t\t\tif (pattern[i + 1] === \"*\") {\n\t\t\t\t// `**/` and `**` cross directory boundaries\n\t\t\t\tif (pattern[i + 2] === \"/\") {\n\t\t\t\t\tregex += \"(?:[^/]+/)*\";\n\t\t\t\t\ti += 2;\n\t\t\t\t} else {\n\t\t\t\t\tregex += \".*\";\n\t\t\t\t\ti += 1;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tregex += \"[^/]*\";\n\t\t\t}\n\t\t} else if (char === \"?\") {\n\t\t\tregex += \"[^/]\";\n\t\t} else if (\"\\\\^$.|+()[]{}\".includes(char)) {\n\t\t\tregex += `\\\\${char}`;\n\t\t} else {\n\t\t\tregex += char;\n\t\t}\n\t}\n\treturn new RegExp(`^${regex}$`);\n}\n\n/** Match a relative path against a glob; patterns without `/` match the basename at any depth. */\nfunction matchGlob(relPath: string, pattern: string): boolean {\n\tconst normalized = relPath.split(\"\\\\\").join(\"/\");\n\tif (!pattern.includes(\"/\")) {\n\t\tconst base = normalized.split(\"/\").pop() ?? normalized;\n\t\treturn globToRegExp(pattern).test(base);\n\t}\n\treturn globToRegExp(pattern).test(normalized);\n}\n\nconst ALWAYS_IGNORED = new Set([\".git\", \"node_modules\"]);\n\n/**\n * Walk files under root depth-first, honoring the root .gitignore (nested\n * .gitignore files are not consulted) and always skipping .git/node_modules.\n * Yields paths relative to root with `/` separators.\n */\nasync function collectFiles(env: NodeExecutionEnv, root: string, limit: number): Promise<string[]> {\n\tconst ig = ignore();\n\ttry {\n\t\tig.add(await readFile(join(root, \".gitignore\"), \"utf-8\"));\n\t} catch {\n\t\t// no .gitignore at the search root\n\t}\n\tconst results: string[] = [];\n\tconst stack: string[] = [\"\"];\n\twhile (stack.length > 0 && results.length < limit) {\n\t\tconst dir = stack.pop()!;\n\t\tlet entries: Awaited<ReturnType<NodeExecutionEnv[\"listDir\"]>>;\n\t\ttry {\n\t\t\tentries = await env.listDir(dir === \"\" ? root : join(root, dir));\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tentries.sort((a, b) => a.name.localeCompare(b.name));\n\t\tfor (const entry of entries) {\n\t\t\tif (ALWAYS_IGNORED.has(entry.name)) continue;\n\t\t\tconst relPath = dir === \"\" ? entry.name : `${dir}/${entry.name}`;\n\t\t\tif (entry.kind === \"directory\") {\n\t\t\t\tif (ig.ignores(`${relPath}/`)) continue;\n\t\t\t\tstack.push(relPath);\n\t\t\t} else if (entry.kind === \"file\") {\n\t\t\t\tif (ig.ignores(relPath)) continue;\n\t\t\t\tresults.push(relPath);\n\t\t\t\tif (results.length >= limit) break;\n\t\t\t}\n\t\t}\n\t}\n\treturn results;\n}\n\n// ---------------------------------------------------------------------------\n// grep\n// ---------------------------------------------------------------------------\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nconst GREP_DEFAULT_LIMIT = 100;\nconst GREP_FILE_SCAN_LIMIT = 50_000;\n\nfunction escapeRegExp(text: string): string {\n\treturn text.replace(/[\\\\^$.|?*+()[\\]{}]/g, \"\\\\$&\");\n}\n\nfunction looksBinary(content: string): boolean {\n\treturn content.includes(\"\\0\");\n}\n\nfunction createGrepTool(env: NodeExecutionEnv, cwd: string): AgentTool<typeof grepSchema> {\n\treturn {\n\t\tname: \"grep\",\n\t\tlabel: \"grep\",\n\t\tdescription: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects the root .gitignore. Output is truncated to ${GREP_DEFAULT_LIMIT} matches by default.`,\n\t\tparameters: grepSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof grepSchema>, signal) => {\n\t\t\tconst source = params.literal ? escapeRegExp(params.pattern) : params.pattern;\n\t\t\tconst regex = new RegExp(source, params.ignoreCase ? \"i\" : \"\");\n\t\t\tconst limit = params.limit ?? GREP_DEFAULT_LIMIT;\n\t\t\tconst searchRoot = resolveToCwd(cwd, params.path ?? \".\");\n\n\t\t\tconst rootInfo = await env.fileInfo(searchRoot);\n\t\t\tconst files =\n\t\t\t\trootInfo.kind === \"file\"\n\t\t\t\t\t? [relative(cwd, searchRoot).split(\"\\\\\").join(\"/\") || rootInfo.name]\n\t\t\t\t\t: await collectFiles(env, searchRoot, GREP_FILE_SCAN_LIMIT);\n\n\t\t\tconst matches: string[] = [];\n\t\t\tlet limitReached = false;\n\t\t\touter: for (const file of files) {\n\t\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\t\t\t\tif (params.glob && !matchGlob(file, params.glob)) continue;\n\t\t\t\tconst absolute = rootInfo.kind === \"file\" ? searchRoot : join(searchRoot, file);\n\t\t\t\tlet content: string;\n\t\t\t\ttry {\n\t\t\t\t\tcontent = await env.readTextFile(absolute);\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (looksBinary(content)) continue;\n\t\t\t\tconst displayPath = rootInfo.kind === \"file\" ? file : relative(cwd, absolute).split(\"\\\\\").join(\"/\");\n\t\t\t\tconst lines = content.split(\"\\n\");\n\t\t\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\t\t\tif (!regex.test(lines[i])) continue;\n\t\t\t\t\tmatches.push(`${displayPath}:${i + 1}: ${truncateLine(lines[i]).text}`);\n\t\t\t\t\tif (matches.length >= limit) {\n\t\t\t\t\t\tlimitReached = true;\n\t\t\t\t\t\tbreak outer;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (matches.length === 0) {\n\t\t\t\treturn textResult(\"No matches found\");\n\t\t\t}\n\t\t\tconst truncation = truncateHead(matches.join(\"\\n\"));\n\t\t\tlet text = truncation.content;\n\t\t\tif (limitReached) {\n\t\t\t\ttext = `${text}\\n[Match limit of ${limit} reached; refine the pattern or raise limit]`;\n\t\t\t} else if (truncation.truncated) {\n\t\t\t\ttext = `${text}\\n[Output truncated at ${formatSize(truncation.maxBytes)}]`;\n\t\t\t}\n\t\t\treturn textResult(text);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// find\n// ---------------------------------------------------------------------------\n\nconst findSchema = Type.Object({\n\tpattern: Type.String({\n\t\tdescription: \"Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.spec.ts'\",\n\t}),\n\tpath: Type.Optional(Type.String({ description: \"Directory to search in (default: current directory)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results (default: 1000)\" })),\n});\n\nconst FIND_DEFAULT_LIMIT = 1000;\n\nfunction createFindTool(env: NodeExecutionEnv, cwd: string): AgentTool<typeof findSchema> {\n\treturn {\n\t\tname: \"find\",\n\t\tlabel: \"find\",\n\t\tdescription: `Search for files by glob pattern. Returns matching file paths relative to the search directory. Respects the root .gitignore. Output is truncated to ${FIND_DEFAULT_LIMIT} results by default.`,\n\t\tparameters: findSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof findSchema>) => {\n\t\t\tconst limit = params.limit ?? FIND_DEFAULT_LIMIT;\n\t\t\tconst searchRoot = resolveToCwd(cwd, params.path ?? \".\");\n\t\t\tconst files = await collectFiles(env, searchRoot, GREP_FILE_SCAN_LIMIT);\n\t\t\tconst matched: string[] = [];\n\t\t\tlet limitReached = false;\n\t\t\tfor (const file of files) {\n\t\t\t\tif (!matchGlob(file, params.pattern)) continue;\n\t\t\t\tmatched.push(file);\n\t\t\t\tif (matched.length >= limit) {\n\t\t\t\t\tlimitReached = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (matched.length === 0) {\n\t\t\t\treturn textResult(\"No files found\");\n\t\t\t}\n\t\t\tlet text = matched.join(\"\\n\");\n\t\t\tif (limitReached) {\n\t\t\t\ttext = `${text}\\n[Result limit of ${limit} reached]`;\n\t\t\t}\n\t\t\treturn textResult(text);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// ls\n// ---------------------------------------------------------------------------\n\nconst lsSchema = Type.Object({\n\tpath: Type.Optional(Type.String({ description: \"Directory to list (default: current directory)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of entries to return (default: 500)\" })),\n});\n\nconst LS_DEFAULT_LIMIT = 500;\n\nfunction createLsTool(env: NodeExecutionEnv, cwd: string): AgentTool<typeof lsSchema> {\n\treturn {\n\t\tname: \"ls\",\n\t\tlabel: \"ls\",\n\t\tdescription: `List directory contents. Returns entries sorted alphabetically, with '/' suffix for directories. Includes dotfiles. Output is truncated to ${LS_DEFAULT_LIMIT} entries by default.`,\n\t\tparameters: lsSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof lsSchema>) => {\n\t\t\tconst target = resolveToCwd(cwd, params.path ?? \".\");\n\t\t\tconst limit = params.limit ?? LS_DEFAULT_LIMIT;\n\t\t\tconst info = await env.fileInfo(target);\n\t\t\tif (info.kind !== \"directory\") {\n\t\t\t\tthrow new FileError(\"not_directory\", `Not a directory: ${target}`, target);\n\t\t\t}\n\t\t\tconst entries = await env.listDir(target);\n\t\t\tentries.sort((a, b) => a.name.localeCompare(b.name));\n\t\t\tconst shown = entries.slice(0, limit);\n\t\t\tlet text = shown.map((entry) => (entry.kind === \"directory\" ? `${entry.name}/` : entry.name)).join(\"\\n\");\n\t\t\tif (entries.length > limit) {\n\t\t\t\ttext = `${text}\\n[Entry limit of ${limit} reached; ${entries.length - limit} more entries]`;\n\t\t\t}\n\t\t\treturn textResult(text.length > 0 ? text : \"(empty directory)\");\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// bundle\n// ---------------------------------------------------------------------------\n\n/**\n * Build the default headless tool bundle (bash/read/edit/write/grep/find/ls)\n * bound to the given working directory.\n *\n * The CLI's Task tool is intentionally not part of this bundle: it requires\n * the CLI's subagent runtime (agent registry, subagent pool, session\n * services), which does not exist in a standalone process.\n */\nexport function getDefaultTools(opts?: DefaultToolsOptions): AgentTool<any>[] {\n\tconst cwd = resolve(opts?.cwd ?? process.cwd());\n\tconst env = new NodeExecutionEnv({ cwd });\n\treturn [\n\t\tcreateBashTool(env),\n\t\tcreateReadTool(env),\n\t\tcreateEditTool(env),\n\t\tcreateWriteTool(env),\n\t\tcreateGrepTool(env, cwd),\n\t\tcreateFindTool(env, cwd),\n\t\tcreateLsTool(env, cwd),\n\t];\n}\n"]}
1
+ {"version":3,"file":"default-tools.d.ts","sourceRoot":"","sources":["../../src/tools/default-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAYH,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,aAAa,CAAC;AAE9D,MAAM,WAAW,mBAAmB;IACnC,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAoMD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAI5E","sourcesContent":["/**\n * Headless default tool bundle: the same built-in tools the hoocode CLI\n * registers (bash/read/edit/write), implemented without any CLI or TUI\n * dependency so they can run in a separate process (for example a hooteams\n * worker). The CLI keeps its own richer implementations with\n * interactive rendering; these share the tool names and parameter contracts.\n *\n * No singletons, no top-level side effects: every call to getDefaultTools()\n * builds a fresh bundle bound to the given cwd.\n */\n\nimport { resolve } from \"node:path\";\nimport { type Static, Type } from \"typebox\";\nimport { NodeExecutionEnv } from \"../harness/env/nodejs.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttruncateHead,\n\ttruncateTail,\n} from \"../harness/utils/truncate.js\";\nimport type { AgentTool, AgentToolResult } from \"../types.js\";\n\nexport interface DefaultToolsOptions {\n\t/** Working directory the tools operate in. Defaults to process.cwd(). */\n\tcwd?: string;\n}\n\nfunction textResult(text: string): AgentToolResult<undefined> {\n\treturn { content: [{ type: \"text\", text }], details: undefined };\n}\n\n// ---------------------------------------------------------------------------\n// bash\n// ---------------------------------------------------------------------------\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\nfunction createBashTool(env: NodeExecutionEnv): AgentTool<typeof bashSchema> {\n\treturn {\n\t\tname: \"bash\",\n\t\tlabel: \"bash\",\n\t\tdescription: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Optionally provide a timeout in seconds.`,\n\t\tparameters: bashSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof bashSchema>, signal) => {\n\t\t\tlet combined = \"\";\n\t\t\tlet exitCode: number;\n\t\t\ttry {\n\t\t\t\tconst result = await env.exec(params.command, {\n\t\t\t\t\ttimeout: params.timeout,\n\t\t\t\t\tsignal,\n\t\t\t\t\tonStdout: (chunk) => {\n\t\t\t\t\t\tcombined += chunk;\n\t\t\t\t\t},\n\t\t\t\t\tonStderr: (chunk) => {\n\t\t\t\t\t\tcombined += chunk;\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\texitCode = result.exitCode;\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tif (message.startsWith(\"timeout:\")) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Command timed out after ${params.timeout}s${combined ? `\\nOutput so far:\\n${combined}` : \"\"}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tconst truncation = truncateTail(combined);\n\t\t\tlet text = truncation.content;\n\t\t\tif (truncation.truncated) {\n\t\t\t\ttext = `[Output truncated: showing last ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize(truncation.totalBytes)})]\\n${text}`;\n\t\t\t}\n\t\t\tif (exitCode !== 0) {\n\t\t\t\ttext = text.length > 0 ? `${text}\\nExit code: ${exitCode}` : `Exit code: ${exitCode}`;\n\t\t\t}\n\t\t\treturn textResult(text.length > 0 ? text : \"(no output)\");\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// read\n// ---------------------------------------------------------------------------\n\nconst readSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nfunction createReadTool(env: NodeExecutionEnv): AgentTool<typeof readSchema> {\n\treturn {\n\t\tname: \"read\",\n\t\tlabel: \"read\",\n\t\tdescription: `Read the contents of a text file. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,\n\t\tparameters: readSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof readSchema>) => {\n\t\t\tconst content = await env.readTextFile(params.path);\n\t\t\tlet lines = content.split(\"\\n\");\n\t\t\tconst totalLines = lines.length;\n\t\t\tconst offset = params.offset !== undefined ? Math.max(1, Math.floor(params.offset)) : 1;\n\t\t\tif (offset > totalLines) {\n\t\t\t\tthrow new Error(`Offset ${offset} is past the end of the file (${totalLines} lines)`);\n\t\t\t}\n\t\t\tlines = lines.slice(offset - 1);\n\t\t\tif (params.limit !== undefined) {\n\t\t\t\tlines = lines.slice(0, Math.max(0, Math.floor(params.limit)));\n\t\t\t}\n\t\t\tconst truncation = truncateHead(lines.join(\"\\n\"));\n\t\t\tlet text = truncation.content;\n\t\t\tif (truncation.truncated) {\n\t\t\t\tconst lastShown = offset - 1 + truncation.outputLines;\n\t\t\t\ttext = `${text}\\n[Truncated: showing lines ${offset}-${lastShown} of ${totalLines}. Continue with offset=${lastShown + 1}]`;\n\t\t\t}\n\t\t\treturn textResult(text);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// edit\n// ---------------------------------------------------------------------------\n\nconst replaceEditSchema = Type.Object(\n\t{\n\t\toldText: Type.String({\n\t\t\tdescription:\n\t\t\t\t\"Exact text for one targeted replacement. It must be unique in the original file and must not overlap with any other edits[].oldText in the same call.\",\n\t\t}),\n\t\tnewText: Type.String({ description: \"Replacement text for this targeted edit.\" }),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst editSchema = Type.Object(\n\t{\n\t\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\t\tedits: Type.Array(replaceEditSchema, {\n\t\t\tdescription:\n\t\t\t\t\"One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits.\",\n\t\t}),\n\t},\n\t{ additionalProperties: false },\n);\n\nfunction countOccurrences(haystack: string, needle: string): number {\n\tif (needle.length === 0) return 0;\n\tlet count = 0;\n\tlet index = haystack.indexOf(needle);\n\twhile (index !== -1) {\n\t\tcount++;\n\t\tindex = haystack.indexOf(needle, index + needle.length);\n\t}\n\treturn count;\n}\n\nfunction createEditTool(env: NodeExecutionEnv): AgentTool<typeof editSchema> {\n\treturn {\n\t\tname: \"edit\",\n\t\tlabel: \"edit\",\n\t\tdescription:\n\t\t\t\"Edit a file by replacing exact text. Each edit's oldText must appear exactly once in the file. Provide multiple edits to make several targeted replacements in one call.\",\n\t\tparameters: editSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof editSchema>) => {\n\t\t\tconst original = await env.readTextFile(params.path);\n\t\t\tif (params.edits.length === 0) {\n\t\t\t\tthrow new Error(\"No edits provided\");\n\t\t\t}\n\t\t\tlet content = original;\n\t\t\tfor (const [index, edit] of params.edits.entries()) {\n\t\t\t\tconst occurrences = countOccurrences(original, edit.oldText);\n\t\t\t\tif (occurrences === 0) {\n\t\t\t\t\tthrow new Error(`edits[${index}].oldText not found in ${params.path}`);\n\t\t\t\t}\n\t\t\t\tif (occurrences > 1) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`edits[${index}].oldText matches ${occurrences} locations in ${params.path}; add surrounding context to make it unique`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (!content.includes(edit.oldText)) {\n\t\t\t\t\tthrow new Error(`edits[${index}].oldText overlaps with an earlier edit in the same call`);\n\t\t\t\t}\n\t\t\t\tcontent = content.replace(edit.oldText, edit.newText);\n\t\t\t}\n\t\t\tawait env.writeFile(params.path, content);\n\t\t\treturn textResult(\n\t\t\t\t`Applied ${params.edits.length} edit${params.edits.length === 1 ? \"\" : \"s\"} to ${params.path}`,\n\t\t\t);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// write\n// ---------------------------------------------------------------------------\n\nconst writeSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n\tcontent: Type.String({ description: \"Content to write to the file\" }),\n});\n\nfunction createWriteTool(env: NodeExecutionEnv): AgentTool<typeof writeSchema> {\n\treturn {\n\t\tname: \"write\",\n\t\tlabel: \"write\",\n\t\tdescription: \"Write content to a file, creating parent directories as needed. Overwrites existing files.\",\n\t\tparameters: writeSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof writeSchema>) => {\n\t\t\tawait env.writeFile(params.path, params.content);\n\t\t\treturn textResult(`Wrote ${formatSize(Buffer.byteLength(params.content, \"utf-8\"))} to ${params.path}`);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// bundle\n// ---------------------------------------------------------------------------\n\n/**\n * Build the default headless tool bundle (bash/read/edit/write) bound to the\n * given working directory.\n *\n * The CLI's Task tool is intentionally not part of this bundle: it requires\n * the CLI's subagent runtime (agent registry, subagent pool, session\n * services), which does not exist in a standalone process.\n */\nexport function getDefaultTools(opts?: DefaultToolsOptions): AgentTool<any>[] {\n\tconst cwd = resolve(opts?.cwd ?? process.cwd());\n\tconst env = new NodeExecutionEnv({ cwd });\n\treturn [createBashTool(env), createReadTool(env), createEditTool(env), createWriteTool(env)];\n}\n"]}
@@ -1,26 +1,20 @@
1
1
  /**
2
2
  * Headless default tool bundle: the same built-in tools the hoocode CLI
3
- * registers (bash/read/edit/write/grep/find/ls), implemented without any
4
- * CLI or TUI dependency so they can run in a separate process (for example
5
- * a hooteams worker). The CLI keeps its own richer implementations with
3
+ * registers (bash/read/edit/write), implemented without any CLI or TUI
4
+ * dependency so they can run in a separate process (for example a hooteams
5
+ * worker). The CLI keeps its own richer implementations with
6
6
  * interactive rendering; these share the tool names and parameter contracts.
7
7
  *
8
8
  * No singletons, no top-level side effects: every call to getDefaultTools()
9
9
  * builds a fresh bundle bound to the given cwd.
10
10
  */
11
- import { readFile } from "node:fs/promises";
12
- import { isAbsolute, join, relative, resolve } from "node:path";
13
- import ignore from "ignore";
11
+ import { resolve } from "node:path";
14
12
  import { Type } from "typebox";
15
13
  import { NodeExecutionEnv } from "../harness/env/nodejs.js";
16
- import { FileError } from "../harness/types.js";
17
- import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead, truncateLine, truncateTail, } from "../harness/utils/truncate.js";
14
+ import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead, truncateTail, } from "../harness/utils/truncate.js";
18
15
  function textResult(text) {
19
16
  return { content: [{ type: "text", text }], details: undefined };
20
17
  }
21
- function resolveToCwd(cwd, path) {
22
- return isAbsolute(path) ? path : resolve(cwd, path);
23
- }
24
18
  // ---------------------------------------------------------------------------
25
19
  // bash
26
20
  // ---------------------------------------------------------------------------
@@ -181,255 +175,11 @@ function createWriteTool(env) {
181
175
  };
182
176
  }
183
177
  // ---------------------------------------------------------------------------
184
- // shared file walking for grep/find
185
- // ---------------------------------------------------------------------------
186
- /** Convert a glob pattern to a regular expression over `/`-separated paths. */
187
- export function globToRegExp(pattern) {
188
- let regex = "";
189
- for (let i = 0; i < pattern.length; i++) {
190
- const char = pattern[i];
191
- if (char === "*") {
192
- if (pattern[i + 1] === "*") {
193
- // `**/` and `**` cross directory boundaries
194
- if (pattern[i + 2] === "/") {
195
- regex += "(?:[^/]+/)*";
196
- i += 2;
197
- }
198
- else {
199
- regex += ".*";
200
- i += 1;
201
- }
202
- }
203
- else {
204
- regex += "[^/]*";
205
- }
206
- }
207
- else if (char === "?") {
208
- regex += "[^/]";
209
- }
210
- else if ("\\^$.|+()[]{}".includes(char)) {
211
- regex += `\\${char}`;
212
- }
213
- else {
214
- regex += char;
215
- }
216
- }
217
- return new RegExp(`^${regex}$`);
218
- }
219
- /** Match a relative path against a glob; patterns without `/` match the basename at any depth. */
220
- function matchGlob(relPath, pattern) {
221
- const normalized = relPath.split("\\").join("/");
222
- if (!pattern.includes("/")) {
223
- const base = normalized.split("/").pop() ?? normalized;
224
- return globToRegExp(pattern).test(base);
225
- }
226
- return globToRegExp(pattern).test(normalized);
227
- }
228
- const ALWAYS_IGNORED = new Set([".git", "node_modules"]);
229
- /**
230
- * Walk files under root depth-first, honoring the root .gitignore (nested
231
- * .gitignore files are not consulted) and always skipping .git/node_modules.
232
- * Yields paths relative to root with `/` separators.
233
- */
234
- async function collectFiles(env, root, limit) {
235
- const ig = ignore();
236
- try {
237
- ig.add(await readFile(join(root, ".gitignore"), "utf-8"));
238
- }
239
- catch {
240
- // no .gitignore at the search root
241
- }
242
- const results = [];
243
- const stack = [""];
244
- while (stack.length > 0 && results.length < limit) {
245
- const dir = stack.pop();
246
- let entries;
247
- try {
248
- entries = await env.listDir(dir === "" ? root : join(root, dir));
249
- }
250
- catch {
251
- continue;
252
- }
253
- entries.sort((a, b) => a.name.localeCompare(b.name));
254
- for (const entry of entries) {
255
- if (ALWAYS_IGNORED.has(entry.name))
256
- continue;
257
- const relPath = dir === "" ? entry.name : `${dir}/${entry.name}`;
258
- if (entry.kind === "directory") {
259
- if (ig.ignores(`${relPath}/`))
260
- continue;
261
- stack.push(relPath);
262
- }
263
- else if (entry.kind === "file") {
264
- if (ig.ignores(relPath))
265
- continue;
266
- results.push(relPath);
267
- if (results.length >= limit)
268
- break;
269
- }
270
- }
271
- }
272
- return results;
273
- }
274
- // ---------------------------------------------------------------------------
275
- // grep
276
- // ---------------------------------------------------------------------------
277
- const grepSchema = Type.Object({
278
- pattern: Type.String({ description: "Search pattern (regex or literal string)" }),
279
- path: Type.Optional(Type.String({ description: "Directory or file to search (default: current directory)" })),
280
- glob: Type.Optional(Type.String({ description: "Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'" })),
281
- ignoreCase: Type.Optional(Type.Boolean({ description: "Case-insensitive search (default: false)" })),
282
- literal: Type.Optional(Type.Boolean({ description: "Treat pattern as literal string instead of regex (default: false)" })),
283
- limit: Type.Optional(Type.Number({ description: "Maximum number of matches to return (default: 100)" })),
284
- });
285
- const GREP_DEFAULT_LIMIT = 100;
286
- const GREP_FILE_SCAN_LIMIT = 50_000;
287
- function escapeRegExp(text) {
288
- return text.replace(/[\\^$.|?*+()[\]{}]/g, "\\$&");
289
- }
290
- function looksBinary(content) {
291
- return content.includes("\0");
292
- }
293
- function createGrepTool(env, cwd) {
294
- return {
295
- name: "grep",
296
- label: "grep",
297
- description: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects the root .gitignore. Output is truncated to ${GREP_DEFAULT_LIMIT} matches by default.`,
298
- parameters: grepSchema,
299
- execute: async (_toolCallId, params, signal) => {
300
- const source = params.literal ? escapeRegExp(params.pattern) : params.pattern;
301
- const regex = new RegExp(source, params.ignoreCase ? "i" : "");
302
- const limit = params.limit ?? GREP_DEFAULT_LIMIT;
303
- const searchRoot = resolveToCwd(cwd, params.path ?? ".");
304
- const rootInfo = await env.fileInfo(searchRoot);
305
- const files = rootInfo.kind === "file"
306
- ? [relative(cwd, searchRoot).split("\\").join("/") || rootInfo.name]
307
- : await collectFiles(env, searchRoot, GREP_FILE_SCAN_LIMIT);
308
- const matches = [];
309
- let limitReached = false;
310
- outer: for (const file of files) {
311
- if (signal?.aborted)
312
- throw new Error("aborted");
313
- if (params.glob && !matchGlob(file, params.glob))
314
- continue;
315
- const absolute = rootInfo.kind === "file" ? searchRoot : join(searchRoot, file);
316
- let content;
317
- try {
318
- content = await env.readTextFile(absolute);
319
- }
320
- catch {
321
- continue;
322
- }
323
- if (looksBinary(content))
324
- continue;
325
- const displayPath = rootInfo.kind === "file" ? file : relative(cwd, absolute).split("\\").join("/");
326
- const lines = content.split("\n");
327
- for (let i = 0; i < lines.length; i++) {
328
- if (!regex.test(lines[i]))
329
- continue;
330
- matches.push(`${displayPath}:${i + 1}: ${truncateLine(lines[i]).text}`);
331
- if (matches.length >= limit) {
332
- limitReached = true;
333
- break outer;
334
- }
335
- }
336
- }
337
- if (matches.length === 0) {
338
- return textResult("No matches found");
339
- }
340
- const truncation = truncateHead(matches.join("\n"));
341
- let text = truncation.content;
342
- if (limitReached) {
343
- text = `${text}\n[Match limit of ${limit} reached; refine the pattern or raise limit]`;
344
- }
345
- else if (truncation.truncated) {
346
- text = `${text}\n[Output truncated at ${formatSize(truncation.maxBytes)}]`;
347
- }
348
- return textResult(text);
349
- },
350
- };
351
- }
352
- // ---------------------------------------------------------------------------
353
- // find
354
- // ---------------------------------------------------------------------------
355
- const findSchema = Type.Object({
356
- pattern: Type.String({
357
- description: "Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.spec.ts'",
358
- }),
359
- path: Type.Optional(Type.String({ description: "Directory to search in (default: current directory)" })),
360
- limit: Type.Optional(Type.Number({ description: "Maximum number of results (default: 1000)" })),
361
- });
362
- const FIND_DEFAULT_LIMIT = 1000;
363
- function createFindTool(env, cwd) {
364
- return {
365
- name: "find",
366
- label: "find",
367
- description: `Search for files by glob pattern. Returns matching file paths relative to the search directory. Respects the root .gitignore. Output is truncated to ${FIND_DEFAULT_LIMIT} results by default.`,
368
- parameters: findSchema,
369
- execute: async (_toolCallId, params) => {
370
- const limit = params.limit ?? FIND_DEFAULT_LIMIT;
371
- const searchRoot = resolveToCwd(cwd, params.path ?? ".");
372
- const files = await collectFiles(env, searchRoot, GREP_FILE_SCAN_LIMIT);
373
- const matched = [];
374
- let limitReached = false;
375
- for (const file of files) {
376
- if (!matchGlob(file, params.pattern))
377
- continue;
378
- matched.push(file);
379
- if (matched.length >= limit) {
380
- limitReached = true;
381
- break;
382
- }
383
- }
384
- if (matched.length === 0) {
385
- return textResult("No files found");
386
- }
387
- let text = matched.join("\n");
388
- if (limitReached) {
389
- text = `${text}\n[Result limit of ${limit} reached]`;
390
- }
391
- return textResult(text);
392
- },
393
- };
394
- }
395
- // ---------------------------------------------------------------------------
396
- // ls
397
- // ---------------------------------------------------------------------------
398
- const lsSchema = Type.Object({
399
- path: Type.Optional(Type.String({ description: "Directory to list (default: current directory)" })),
400
- limit: Type.Optional(Type.Number({ description: "Maximum number of entries to return (default: 500)" })),
401
- });
402
- const LS_DEFAULT_LIMIT = 500;
403
- function createLsTool(env, cwd) {
404
- return {
405
- name: "ls",
406
- label: "ls",
407
- description: `List directory contents. Returns entries sorted alphabetically, with '/' suffix for directories. Includes dotfiles. Output is truncated to ${LS_DEFAULT_LIMIT} entries by default.`,
408
- parameters: lsSchema,
409
- execute: async (_toolCallId, params) => {
410
- const target = resolveToCwd(cwd, params.path ?? ".");
411
- const limit = params.limit ?? LS_DEFAULT_LIMIT;
412
- const info = await env.fileInfo(target);
413
- if (info.kind !== "directory") {
414
- throw new FileError("not_directory", `Not a directory: ${target}`, target);
415
- }
416
- const entries = await env.listDir(target);
417
- entries.sort((a, b) => a.name.localeCompare(b.name));
418
- const shown = entries.slice(0, limit);
419
- let text = shown.map((entry) => (entry.kind === "directory" ? `${entry.name}/` : entry.name)).join("\n");
420
- if (entries.length > limit) {
421
- text = `${text}\n[Entry limit of ${limit} reached; ${entries.length - limit} more entries]`;
422
- }
423
- return textResult(text.length > 0 ? text : "(empty directory)");
424
- },
425
- };
426
- }
427
- // ---------------------------------------------------------------------------
428
178
  // bundle
429
179
  // ---------------------------------------------------------------------------
430
180
  /**
431
- * Build the default headless tool bundle (bash/read/edit/write/grep/find/ls)
432
- * bound to the given working directory.
181
+ * Build the default headless tool bundle (bash/read/edit/write) bound to the
182
+ * given working directory.
433
183
  *
434
184
  * The CLI's Task tool is intentionally not part of this bundle: it requires
435
185
  * the CLI's subagent runtime (agent registry, subagent pool, session
@@ -438,14 +188,6 @@ function createLsTool(env, cwd) {
438
188
  export function getDefaultTools(opts) {
439
189
  const cwd = resolve(opts?.cwd ?? process.cwd());
440
190
  const env = new NodeExecutionEnv({ cwd });
441
- return [
442
- createBashTool(env),
443
- createReadTool(env),
444
- createEditTool(env),
445
- createWriteTool(env),
446
- createGrepTool(env, cwd),
447
- createFindTool(env, cwd),
448
- createLsTool(env, cwd),
449
- ];
191
+ return [createBashTool(env), createReadTool(env), createEditTool(env), createWriteTool(env)];
450
192
  }
451
193
  //# sourceMappingURL=default-tools.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"default-tools.js","sourceRoot":"","sources":["../../src/tools/default-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Headless default tool bundle: the same built-in tools the hoocode CLI\n * registers (bash/read/edit/write/grep/find/ls), implemented without any\n * CLI or TUI dependency so they can run in a separate process (for example\n * a hooteams worker). The CLI keeps its own richer implementations with\n * interactive rendering; these share the tool names and parameter contracts.\n *\n * No singletons, no top-level side effects: every call to getDefaultTools()\n * builds a fresh bundle bound to the given cwd.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { isAbsolute, join, relative, resolve } from \"node:path\";\nimport ignore from \"ignore\";\nimport { type Static, Type } from \"typebox\";\nimport { NodeExecutionEnv } from \"../harness/env/nodejs.js\";\nimport { FileError } from \"../harness/types.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttruncateHead,\n\ttruncateLine,\n\ttruncateTail,\n} from \"../harness/utils/truncate.js\";\nimport type { AgentTool, AgentToolResult } from \"../types.js\";\n\nexport interface DefaultToolsOptions {\n\t/** Working directory the tools operate in. Defaults to process.cwd(). */\n\tcwd?: string;\n}\n\nfunction textResult(text: string): AgentToolResult<undefined> {\n\treturn { content: [{ type: \"text\", text }], details: undefined };\n}\n\nfunction resolveToCwd(cwd: string, path: string): string {\n\treturn isAbsolute(path) ? path : resolve(cwd, path);\n}\n\n// ---------------------------------------------------------------------------\n// bash\n// ---------------------------------------------------------------------------\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\nfunction createBashTool(env: NodeExecutionEnv): AgentTool<typeof bashSchema> {\n\treturn {\n\t\tname: \"bash\",\n\t\tlabel: \"bash\",\n\t\tdescription: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Optionally provide a timeout in seconds.`,\n\t\tparameters: bashSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof bashSchema>, signal) => {\n\t\t\tlet combined = \"\";\n\t\t\tlet exitCode: number;\n\t\t\ttry {\n\t\t\t\tconst result = await env.exec(params.command, {\n\t\t\t\t\ttimeout: params.timeout,\n\t\t\t\t\tsignal,\n\t\t\t\t\tonStdout: (chunk) => {\n\t\t\t\t\t\tcombined += chunk;\n\t\t\t\t\t},\n\t\t\t\t\tonStderr: (chunk) => {\n\t\t\t\t\t\tcombined += chunk;\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\texitCode = result.exitCode;\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tif (message.startsWith(\"timeout:\")) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Command timed out after ${params.timeout}s${combined ? `\\nOutput so far:\\n${combined}` : \"\"}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tconst truncation = truncateTail(combined);\n\t\t\tlet text = truncation.content;\n\t\t\tif (truncation.truncated) {\n\t\t\t\ttext = `[Output truncated: showing last ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize(truncation.totalBytes)})]\\n${text}`;\n\t\t\t}\n\t\t\tif (exitCode !== 0) {\n\t\t\t\ttext = text.length > 0 ? `${text}\\nExit code: ${exitCode}` : `Exit code: ${exitCode}`;\n\t\t\t}\n\t\t\treturn textResult(text.length > 0 ? text : \"(no output)\");\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// read\n// ---------------------------------------------------------------------------\n\nconst readSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nfunction createReadTool(env: NodeExecutionEnv): AgentTool<typeof readSchema> {\n\treturn {\n\t\tname: \"read\",\n\t\tlabel: \"read\",\n\t\tdescription: `Read the contents of a text file. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,\n\t\tparameters: readSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof readSchema>) => {\n\t\t\tconst content = await env.readTextFile(params.path);\n\t\t\tlet lines = content.split(\"\\n\");\n\t\t\tconst totalLines = lines.length;\n\t\t\tconst offset = params.offset !== undefined ? Math.max(1, Math.floor(params.offset)) : 1;\n\t\t\tif (offset > totalLines) {\n\t\t\t\tthrow new Error(`Offset ${offset} is past the end of the file (${totalLines} lines)`);\n\t\t\t}\n\t\t\tlines = lines.slice(offset - 1);\n\t\t\tif (params.limit !== undefined) {\n\t\t\t\tlines = lines.slice(0, Math.max(0, Math.floor(params.limit)));\n\t\t\t}\n\t\t\tconst truncation = truncateHead(lines.join(\"\\n\"));\n\t\t\tlet text = truncation.content;\n\t\t\tif (truncation.truncated) {\n\t\t\t\tconst lastShown = offset - 1 + truncation.outputLines;\n\t\t\t\ttext = `${text}\\n[Truncated: showing lines ${offset}-${lastShown} of ${totalLines}. Continue with offset=${lastShown + 1}]`;\n\t\t\t}\n\t\t\treturn textResult(text);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// edit\n// ---------------------------------------------------------------------------\n\nconst replaceEditSchema = Type.Object(\n\t{\n\t\toldText: Type.String({\n\t\t\tdescription:\n\t\t\t\t\"Exact text for one targeted replacement. It must be unique in the original file and must not overlap with any other edits[].oldText in the same call.\",\n\t\t}),\n\t\tnewText: Type.String({ description: \"Replacement text for this targeted edit.\" }),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst editSchema = Type.Object(\n\t{\n\t\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\t\tedits: Type.Array(replaceEditSchema, {\n\t\t\tdescription:\n\t\t\t\t\"One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits.\",\n\t\t}),\n\t},\n\t{ additionalProperties: false },\n);\n\nfunction countOccurrences(haystack: string, needle: string): number {\n\tif (needle.length === 0) return 0;\n\tlet count = 0;\n\tlet index = haystack.indexOf(needle);\n\twhile (index !== -1) {\n\t\tcount++;\n\t\tindex = haystack.indexOf(needle, index + needle.length);\n\t}\n\treturn count;\n}\n\nfunction createEditTool(env: NodeExecutionEnv): AgentTool<typeof editSchema> {\n\treturn {\n\t\tname: \"edit\",\n\t\tlabel: \"edit\",\n\t\tdescription:\n\t\t\t\"Edit a file by replacing exact text. Each edit's oldText must appear exactly once in the file. Provide multiple edits to make several targeted replacements in one call.\",\n\t\tparameters: editSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof editSchema>) => {\n\t\t\tconst original = await env.readTextFile(params.path);\n\t\t\tif (params.edits.length === 0) {\n\t\t\t\tthrow new Error(\"No edits provided\");\n\t\t\t}\n\t\t\tlet content = original;\n\t\t\tfor (const [index, edit] of params.edits.entries()) {\n\t\t\t\tconst occurrences = countOccurrences(original, edit.oldText);\n\t\t\t\tif (occurrences === 0) {\n\t\t\t\t\tthrow new Error(`edits[${index}].oldText not found in ${params.path}`);\n\t\t\t\t}\n\t\t\t\tif (occurrences > 1) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`edits[${index}].oldText matches ${occurrences} locations in ${params.path}; add surrounding context to make it unique`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (!content.includes(edit.oldText)) {\n\t\t\t\t\tthrow new Error(`edits[${index}].oldText overlaps with an earlier edit in the same call`);\n\t\t\t\t}\n\t\t\t\tcontent = content.replace(edit.oldText, edit.newText);\n\t\t\t}\n\t\t\tawait env.writeFile(params.path, content);\n\t\t\treturn textResult(\n\t\t\t\t`Applied ${params.edits.length} edit${params.edits.length === 1 ? \"\" : \"s\"} to ${params.path}`,\n\t\t\t);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// write\n// ---------------------------------------------------------------------------\n\nconst writeSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n\tcontent: Type.String({ description: \"Content to write to the file\" }),\n});\n\nfunction createWriteTool(env: NodeExecutionEnv): AgentTool<typeof writeSchema> {\n\treturn {\n\t\tname: \"write\",\n\t\tlabel: \"write\",\n\t\tdescription: \"Write content to a file, creating parent directories as needed. Overwrites existing files.\",\n\t\tparameters: writeSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof writeSchema>) => {\n\t\t\tawait env.writeFile(params.path, params.content);\n\t\t\treturn textResult(`Wrote ${formatSize(Buffer.byteLength(params.content, \"utf-8\"))} to ${params.path}`);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// shared file walking for grep/find\n// ---------------------------------------------------------------------------\n\n/** Convert a glob pattern to a regular expression over `/`-separated paths. */\nexport function globToRegExp(pattern: string): RegExp {\n\tlet regex = \"\";\n\tfor (let i = 0; i < pattern.length; i++) {\n\t\tconst char = pattern[i];\n\t\tif (char === \"*\") {\n\t\t\tif (pattern[i + 1] === \"*\") {\n\t\t\t\t// `**/` and `**` cross directory boundaries\n\t\t\t\tif (pattern[i + 2] === \"/\") {\n\t\t\t\t\tregex += \"(?:[^/]+/)*\";\n\t\t\t\t\ti += 2;\n\t\t\t\t} else {\n\t\t\t\t\tregex += \".*\";\n\t\t\t\t\ti += 1;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tregex += \"[^/]*\";\n\t\t\t}\n\t\t} else if (char === \"?\") {\n\t\t\tregex += \"[^/]\";\n\t\t} else if (\"\\\\^$.|+()[]{}\".includes(char)) {\n\t\t\tregex += `\\\\${char}`;\n\t\t} else {\n\t\t\tregex += char;\n\t\t}\n\t}\n\treturn new RegExp(`^${regex}$`);\n}\n\n/** Match a relative path against a glob; patterns without `/` match the basename at any depth. */\nfunction matchGlob(relPath: string, pattern: string): boolean {\n\tconst normalized = relPath.split(\"\\\\\").join(\"/\");\n\tif (!pattern.includes(\"/\")) {\n\t\tconst base = normalized.split(\"/\").pop() ?? normalized;\n\t\treturn globToRegExp(pattern).test(base);\n\t}\n\treturn globToRegExp(pattern).test(normalized);\n}\n\nconst ALWAYS_IGNORED = new Set([\".git\", \"node_modules\"]);\n\n/**\n * Walk files under root depth-first, honoring the root .gitignore (nested\n * .gitignore files are not consulted) and always skipping .git/node_modules.\n * Yields paths relative to root with `/` separators.\n */\nasync function collectFiles(env: NodeExecutionEnv, root: string, limit: number): Promise<string[]> {\n\tconst ig = ignore();\n\ttry {\n\t\tig.add(await readFile(join(root, \".gitignore\"), \"utf-8\"));\n\t} catch {\n\t\t// no .gitignore at the search root\n\t}\n\tconst results: string[] = [];\n\tconst stack: string[] = [\"\"];\n\twhile (stack.length > 0 && results.length < limit) {\n\t\tconst dir = stack.pop()!;\n\t\tlet entries: Awaited<ReturnType<NodeExecutionEnv[\"listDir\"]>>;\n\t\ttry {\n\t\t\tentries = await env.listDir(dir === \"\" ? root : join(root, dir));\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tentries.sort((a, b) => a.name.localeCompare(b.name));\n\t\tfor (const entry of entries) {\n\t\t\tif (ALWAYS_IGNORED.has(entry.name)) continue;\n\t\t\tconst relPath = dir === \"\" ? entry.name : `${dir}/${entry.name}`;\n\t\t\tif (entry.kind === \"directory\") {\n\t\t\t\tif (ig.ignores(`${relPath}/`)) continue;\n\t\t\t\tstack.push(relPath);\n\t\t\t} else if (entry.kind === \"file\") {\n\t\t\t\tif (ig.ignores(relPath)) continue;\n\t\t\t\tresults.push(relPath);\n\t\t\t\tif (results.length >= limit) break;\n\t\t\t}\n\t\t}\n\t}\n\treturn results;\n}\n\n// ---------------------------------------------------------------------------\n// grep\n// ---------------------------------------------------------------------------\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nconst GREP_DEFAULT_LIMIT = 100;\nconst GREP_FILE_SCAN_LIMIT = 50_000;\n\nfunction escapeRegExp(text: string): string {\n\treturn text.replace(/[\\\\^$.|?*+()[\\]{}]/g, \"\\\\$&\");\n}\n\nfunction looksBinary(content: string): boolean {\n\treturn content.includes(\"\\0\");\n}\n\nfunction createGrepTool(env: NodeExecutionEnv, cwd: string): AgentTool<typeof grepSchema> {\n\treturn {\n\t\tname: \"grep\",\n\t\tlabel: \"grep\",\n\t\tdescription: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects the root .gitignore. Output is truncated to ${GREP_DEFAULT_LIMIT} matches by default.`,\n\t\tparameters: grepSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof grepSchema>, signal) => {\n\t\t\tconst source = params.literal ? escapeRegExp(params.pattern) : params.pattern;\n\t\t\tconst regex = new RegExp(source, params.ignoreCase ? \"i\" : \"\");\n\t\t\tconst limit = params.limit ?? GREP_DEFAULT_LIMIT;\n\t\t\tconst searchRoot = resolveToCwd(cwd, params.path ?? \".\");\n\n\t\t\tconst rootInfo = await env.fileInfo(searchRoot);\n\t\t\tconst files =\n\t\t\t\trootInfo.kind === \"file\"\n\t\t\t\t\t? [relative(cwd, searchRoot).split(\"\\\\\").join(\"/\") || rootInfo.name]\n\t\t\t\t\t: await collectFiles(env, searchRoot, GREP_FILE_SCAN_LIMIT);\n\n\t\t\tconst matches: string[] = [];\n\t\t\tlet limitReached = false;\n\t\t\touter: for (const file of files) {\n\t\t\t\tif (signal?.aborted) throw new Error(\"aborted\");\n\t\t\t\tif (params.glob && !matchGlob(file, params.glob)) continue;\n\t\t\t\tconst absolute = rootInfo.kind === \"file\" ? searchRoot : join(searchRoot, file);\n\t\t\t\tlet content: string;\n\t\t\t\ttry {\n\t\t\t\t\tcontent = await env.readTextFile(absolute);\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (looksBinary(content)) continue;\n\t\t\t\tconst displayPath = rootInfo.kind === \"file\" ? file : relative(cwd, absolute).split(\"\\\\\").join(\"/\");\n\t\t\t\tconst lines = content.split(\"\\n\");\n\t\t\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\t\t\tif (!regex.test(lines[i])) continue;\n\t\t\t\t\tmatches.push(`${displayPath}:${i + 1}: ${truncateLine(lines[i]).text}`);\n\t\t\t\t\tif (matches.length >= limit) {\n\t\t\t\t\t\tlimitReached = true;\n\t\t\t\t\t\tbreak outer;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (matches.length === 0) {\n\t\t\t\treturn textResult(\"No matches found\");\n\t\t\t}\n\t\t\tconst truncation = truncateHead(matches.join(\"\\n\"));\n\t\t\tlet text = truncation.content;\n\t\t\tif (limitReached) {\n\t\t\t\ttext = `${text}\\n[Match limit of ${limit} reached; refine the pattern or raise limit]`;\n\t\t\t} else if (truncation.truncated) {\n\t\t\t\ttext = `${text}\\n[Output truncated at ${formatSize(truncation.maxBytes)}]`;\n\t\t\t}\n\t\t\treturn textResult(text);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// find\n// ---------------------------------------------------------------------------\n\nconst findSchema = Type.Object({\n\tpattern: Type.String({\n\t\tdescription: \"Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.spec.ts'\",\n\t}),\n\tpath: Type.Optional(Type.String({ description: \"Directory to search in (default: current directory)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results (default: 1000)\" })),\n});\n\nconst FIND_DEFAULT_LIMIT = 1000;\n\nfunction createFindTool(env: NodeExecutionEnv, cwd: string): AgentTool<typeof findSchema> {\n\treturn {\n\t\tname: \"find\",\n\t\tlabel: \"find\",\n\t\tdescription: `Search for files by glob pattern. Returns matching file paths relative to the search directory. Respects the root .gitignore. Output is truncated to ${FIND_DEFAULT_LIMIT} results by default.`,\n\t\tparameters: findSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof findSchema>) => {\n\t\t\tconst limit = params.limit ?? FIND_DEFAULT_LIMIT;\n\t\t\tconst searchRoot = resolveToCwd(cwd, params.path ?? \".\");\n\t\t\tconst files = await collectFiles(env, searchRoot, GREP_FILE_SCAN_LIMIT);\n\t\t\tconst matched: string[] = [];\n\t\t\tlet limitReached = false;\n\t\t\tfor (const file of files) {\n\t\t\t\tif (!matchGlob(file, params.pattern)) continue;\n\t\t\t\tmatched.push(file);\n\t\t\t\tif (matched.length >= limit) {\n\t\t\t\t\tlimitReached = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (matched.length === 0) {\n\t\t\t\treturn textResult(\"No files found\");\n\t\t\t}\n\t\t\tlet text = matched.join(\"\\n\");\n\t\t\tif (limitReached) {\n\t\t\t\ttext = `${text}\\n[Result limit of ${limit} reached]`;\n\t\t\t}\n\t\t\treturn textResult(text);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// ls\n// ---------------------------------------------------------------------------\n\nconst lsSchema = Type.Object({\n\tpath: Type.Optional(Type.String({ description: \"Directory to list (default: current directory)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of entries to return (default: 500)\" })),\n});\n\nconst LS_DEFAULT_LIMIT = 500;\n\nfunction createLsTool(env: NodeExecutionEnv, cwd: string): AgentTool<typeof lsSchema> {\n\treturn {\n\t\tname: \"ls\",\n\t\tlabel: \"ls\",\n\t\tdescription: `List directory contents. Returns entries sorted alphabetically, with '/' suffix for directories. Includes dotfiles. Output is truncated to ${LS_DEFAULT_LIMIT} entries by default.`,\n\t\tparameters: lsSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof lsSchema>) => {\n\t\t\tconst target = resolveToCwd(cwd, params.path ?? \".\");\n\t\t\tconst limit = params.limit ?? LS_DEFAULT_LIMIT;\n\t\t\tconst info = await env.fileInfo(target);\n\t\t\tif (info.kind !== \"directory\") {\n\t\t\t\tthrow new FileError(\"not_directory\", `Not a directory: ${target}`, target);\n\t\t\t}\n\t\t\tconst entries = await env.listDir(target);\n\t\t\tentries.sort((a, b) => a.name.localeCompare(b.name));\n\t\t\tconst shown = entries.slice(0, limit);\n\t\t\tlet text = shown.map((entry) => (entry.kind === \"directory\" ? `${entry.name}/` : entry.name)).join(\"\\n\");\n\t\t\tif (entries.length > limit) {\n\t\t\t\ttext = `${text}\\n[Entry limit of ${limit} reached; ${entries.length - limit} more entries]`;\n\t\t\t}\n\t\t\treturn textResult(text.length > 0 ? text : \"(empty directory)\");\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// bundle\n// ---------------------------------------------------------------------------\n\n/**\n * Build the default headless tool bundle (bash/read/edit/write/grep/find/ls)\n * bound to the given working directory.\n *\n * The CLI's Task tool is intentionally not part of this bundle: it requires\n * the CLI's subagent runtime (agent registry, subagent pool, session\n * services), which does not exist in a standalone process.\n */\nexport function getDefaultTools(opts?: DefaultToolsOptions): AgentTool<any>[] {\n\tconst cwd = resolve(opts?.cwd ?? process.cwd());\n\tconst env = new NodeExecutionEnv({ cwd });\n\treturn [\n\t\tcreateBashTool(env),\n\t\tcreateReadTool(env),\n\t\tcreateEditTool(env),\n\t\tcreateWriteTool(env),\n\t\tcreateGrepTool(env, cwd),\n\t\tcreateFindTool(env, cwd),\n\t\tcreateLsTool(env, cwd),\n\t];\n}\n"]}
1
+ {"version":3,"file":"default-tools.js","sourceRoot":"","sources":["../../src/tools/default-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Headless default tool bundle: the same built-in tools the hoocode CLI\n * registers (bash/read/edit/write), implemented without any CLI or TUI\n * dependency so they can run in a separate process (for example a hooteams\n * worker). The CLI keeps its own richer implementations with\n * interactive rendering; these share the tool names and parameter contracts.\n *\n * No singletons, no top-level side effects: every call to getDefaultTools()\n * builds a fresh bundle bound to the given cwd.\n */\n\nimport { resolve } from \"node:path\";\nimport { type Static, Type } from \"typebox\";\nimport { NodeExecutionEnv } from \"../harness/env/nodejs.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttruncateHead,\n\ttruncateTail,\n} from \"../harness/utils/truncate.js\";\nimport type { AgentTool, AgentToolResult } from \"../types.js\";\n\nexport interface DefaultToolsOptions {\n\t/** Working directory the tools operate in. Defaults to process.cwd(). */\n\tcwd?: string;\n}\n\nfunction textResult(text: string): AgentToolResult<undefined> {\n\treturn { content: [{ type: \"text\", text }], details: undefined };\n}\n\n// ---------------------------------------------------------------------------\n// bash\n// ---------------------------------------------------------------------------\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\nfunction createBashTool(env: NodeExecutionEnv): AgentTool<typeof bashSchema> {\n\treturn {\n\t\tname: \"bash\",\n\t\tlabel: \"bash\",\n\t\tdescription: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Optionally provide a timeout in seconds.`,\n\t\tparameters: bashSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof bashSchema>, signal) => {\n\t\t\tlet combined = \"\";\n\t\t\tlet exitCode: number;\n\t\t\ttry {\n\t\t\t\tconst result = await env.exec(params.command, {\n\t\t\t\t\ttimeout: params.timeout,\n\t\t\t\t\tsignal,\n\t\t\t\t\tonStdout: (chunk) => {\n\t\t\t\t\t\tcombined += chunk;\n\t\t\t\t\t},\n\t\t\t\t\tonStderr: (chunk) => {\n\t\t\t\t\t\tcombined += chunk;\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\texitCode = result.exitCode;\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tif (message.startsWith(\"timeout:\")) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Command timed out after ${params.timeout}s${combined ? `\\nOutput so far:\\n${combined}` : \"\"}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tconst truncation = truncateTail(combined);\n\t\t\tlet text = truncation.content;\n\t\t\tif (truncation.truncated) {\n\t\t\t\ttext = `[Output truncated: showing last ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize(truncation.totalBytes)})]\\n${text}`;\n\t\t\t}\n\t\t\tif (exitCode !== 0) {\n\t\t\t\ttext = text.length > 0 ? `${text}\\nExit code: ${exitCode}` : `Exit code: ${exitCode}`;\n\t\t\t}\n\t\t\treturn textResult(text.length > 0 ? text : \"(no output)\");\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// read\n// ---------------------------------------------------------------------------\n\nconst readSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nfunction createReadTool(env: NodeExecutionEnv): AgentTool<typeof readSchema> {\n\treturn {\n\t\tname: \"read\",\n\t\tlabel: \"read\",\n\t\tdescription: `Read the contents of a text file. Output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,\n\t\tparameters: readSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof readSchema>) => {\n\t\t\tconst content = await env.readTextFile(params.path);\n\t\t\tlet lines = content.split(\"\\n\");\n\t\t\tconst totalLines = lines.length;\n\t\t\tconst offset = params.offset !== undefined ? Math.max(1, Math.floor(params.offset)) : 1;\n\t\t\tif (offset > totalLines) {\n\t\t\t\tthrow new Error(`Offset ${offset} is past the end of the file (${totalLines} lines)`);\n\t\t\t}\n\t\t\tlines = lines.slice(offset - 1);\n\t\t\tif (params.limit !== undefined) {\n\t\t\t\tlines = lines.slice(0, Math.max(0, Math.floor(params.limit)));\n\t\t\t}\n\t\t\tconst truncation = truncateHead(lines.join(\"\\n\"));\n\t\t\tlet text = truncation.content;\n\t\t\tif (truncation.truncated) {\n\t\t\t\tconst lastShown = offset - 1 + truncation.outputLines;\n\t\t\t\ttext = `${text}\\n[Truncated: showing lines ${offset}-${lastShown} of ${totalLines}. Continue with offset=${lastShown + 1}]`;\n\t\t\t}\n\t\t\treturn textResult(text);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// edit\n// ---------------------------------------------------------------------------\n\nconst replaceEditSchema = Type.Object(\n\t{\n\t\toldText: Type.String({\n\t\t\tdescription:\n\t\t\t\t\"Exact text for one targeted replacement. It must be unique in the original file and must not overlap with any other edits[].oldText in the same call.\",\n\t\t}),\n\t\tnewText: Type.String({ description: \"Replacement text for this targeted edit.\" }),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst editSchema = Type.Object(\n\t{\n\t\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\t\tedits: Type.Array(replaceEditSchema, {\n\t\t\tdescription:\n\t\t\t\t\"One or more targeted replacements. Each edit is matched against the original file, not incrementally. Do not include overlapping or nested edits.\",\n\t\t}),\n\t},\n\t{ additionalProperties: false },\n);\n\nfunction countOccurrences(haystack: string, needle: string): number {\n\tif (needle.length === 0) return 0;\n\tlet count = 0;\n\tlet index = haystack.indexOf(needle);\n\twhile (index !== -1) {\n\t\tcount++;\n\t\tindex = haystack.indexOf(needle, index + needle.length);\n\t}\n\treturn count;\n}\n\nfunction createEditTool(env: NodeExecutionEnv): AgentTool<typeof editSchema> {\n\treturn {\n\t\tname: \"edit\",\n\t\tlabel: \"edit\",\n\t\tdescription:\n\t\t\t\"Edit a file by replacing exact text. Each edit's oldText must appear exactly once in the file. Provide multiple edits to make several targeted replacements in one call.\",\n\t\tparameters: editSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof editSchema>) => {\n\t\t\tconst original = await env.readTextFile(params.path);\n\t\t\tif (params.edits.length === 0) {\n\t\t\t\tthrow new Error(\"No edits provided\");\n\t\t\t}\n\t\t\tlet content = original;\n\t\t\tfor (const [index, edit] of params.edits.entries()) {\n\t\t\t\tconst occurrences = countOccurrences(original, edit.oldText);\n\t\t\t\tif (occurrences === 0) {\n\t\t\t\t\tthrow new Error(`edits[${index}].oldText not found in ${params.path}`);\n\t\t\t\t}\n\t\t\t\tif (occurrences > 1) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`edits[${index}].oldText matches ${occurrences} locations in ${params.path}; add surrounding context to make it unique`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (!content.includes(edit.oldText)) {\n\t\t\t\t\tthrow new Error(`edits[${index}].oldText overlaps with an earlier edit in the same call`);\n\t\t\t\t}\n\t\t\t\tcontent = content.replace(edit.oldText, edit.newText);\n\t\t\t}\n\t\t\tawait env.writeFile(params.path, content);\n\t\t\treturn textResult(\n\t\t\t\t`Applied ${params.edits.length} edit${params.edits.length === 1 ? \"\" : \"s\"} to ${params.path}`,\n\t\t\t);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// write\n// ---------------------------------------------------------------------------\n\nconst writeSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n\tcontent: Type.String({ description: \"Content to write to the file\" }),\n});\n\nfunction createWriteTool(env: NodeExecutionEnv): AgentTool<typeof writeSchema> {\n\treturn {\n\t\tname: \"write\",\n\t\tlabel: \"write\",\n\t\tdescription: \"Write content to a file, creating parent directories as needed. Overwrites existing files.\",\n\t\tparameters: writeSchema,\n\t\texecute: async (_toolCallId, params: Static<typeof writeSchema>) => {\n\t\t\tawait env.writeFile(params.path, params.content);\n\t\t\treturn textResult(`Wrote ${formatSize(Buffer.byteLength(params.content, \"utf-8\"))} to ${params.path}`);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// bundle\n// ---------------------------------------------------------------------------\n\n/**\n * Build the default headless tool bundle (bash/read/edit/write) bound to the\n * given working directory.\n *\n * The CLI's Task tool is intentionally not part of this bundle: it requires\n * the CLI's subagent runtime (agent registry, subagent pool, session\n * services), which does not exist in a standalone process.\n */\nexport function getDefaultTools(opts?: DefaultToolsOptions): AgentTool<any>[] {\n\tconst cwd = resolve(opts?.cwd ?? process.cwd());\n\tconst env = new NodeExecutionEnv({ cwd });\n\treturn [createBashTool(env), createReadTool(env), createEditTool(env), createWriteTool(env)];\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-agent-core",
3
- "version": "0.5.49",
3
+ "version": "0.5.51",
4
4
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,7 +17,7 @@
17
17
  "prepublishOnly": "npm run clean && npm run build"
18
18
  },
19
19
  "dependencies": {
20
- "@kolisachint/hoocode-ai": "^0.5.49",
20
+ "@kolisachint/hoocode-ai": "^0.5.51",
21
21
  "@modelcontextprotocol/sdk": "^1.29.0",
22
22
  "ignore": "^7.0.5",
23
23
  "typebox": "^1.1.24",