chore(opencode): remove scout agent (#30435)
This commit is contained in:
@@ -23,9 +23,6 @@ import { Plugin } from "../plugin"
|
||||
import { Provider } from "@/provider/provider"
|
||||
|
||||
import { WebSearchTool } from "./websearch"
|
||||
import { RepoCloneTool } from "./repo_clone"
|
||||
import { RepoOverviewTool } from "./repo_overview"
|
||||
import { RepositoryCache } from "@/reference/repository-cache"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { LspTool } from "./lsp"
|
||||
import * as Truncate from "./truncate"
|
||||
@@ -48,7 +45,6 @@ import { Instruction } from "../session/instruction"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { Agent } from "../agent/agent"
|
||||
import { Git } from "@/git"
|
||||
import { Skill } from "../skill"
|
||||
import { Permission } from "@/permission"
|
||||
import { Reference } from "@/reference/reference"
|
||||
@@ -97,8 +93,6 @@ export const layer: Layer.Layer<
|
||||
| Session.Service
|
||||
| BackgroundJob.Service
|
||||
| Provider.Service
|
||||
| Git.Service
|
||||
| RepositoryCache.Service
|
||||
| Reference.Service
|
||||
| LSP.Service
|
||||
| Instruction.Service
|
||||
@@ -130,8 +124,6 @@ export const layer: Layer.Layer<
|
||||
const plan = yield* PlanExitTool
|
||||
const webfetch = yield* WebFetchTool
|
||||
const websearch = yield* WebSearchTool
|
||||
const repoClone = yield* RepoCloneTool
|
||||
const repoOverview = yield* RepoOverviewTool
|
||||
const shell = yield* ShellTool
|
||||
const globtool = yield* GlobTool
|
||||
const writetool = yield* WriteTool
|
||||
@@ -241,8 +233,6 @@ export const layer: Layer.Layer<
|
||||
fetch: Tool.init(webfetch),
|
||||
todo: Tool.init(todo),
|
||||
search: Tool.init(websearch),
|
||||
repo_clone: Tool.init(repoClone),
|
||||
repo_overview: Tool.init(repoOverview),
|
||||
skill: Tool.init(skilltool),
|
||||
patch: Tool.init(patchtool),
|
||||
question: Tool.init(question),
|
||||
@@ -265,7 +255,6 @@ export const layer: Layer.Layer<
|
||||
tool.fetch,
|
||||
tool.todo,
|
||||
tool.search,
|
||||
...(flags.experimentalScout ? [tool.repo_clone, tool.repo_overview] : []),
|
||||
tool.skill,
|
||||
tool.patch,
|
||||
...(flags.experimentalLspTool ? [tool.lsp] : []),
|
||||
@@ -388,7 +377,6 @@ export const defaultLayer = Layer.suspend(() =>
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(BackgroundJob.defaultLayer),
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(Layer.mergeAll(Git.defaultLayer, RepositoryCache.defaultLayer)),
|
||||
Layer.provide(Reference.defaultLayer),
|
||||
Layer.provide(LSP.defaultLayer),
|
||||
Layer.provide(Instruction.defaultLayer),
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import { Effect, Schema } from "effect"
|
||||
import DESCRIPTION from "./repo_clone.txt"
|
||||
import * as Tool from "./tool"
|
||||
import { repositoryCachePath } from "@/util/repository"
|
||||
import { RepositoryCache } from "@/reference/repository-cache"
|
||||
|
||||
export const Parameters = Schema.Struct({
|
||||
repository: Schema.String.annotate({
|
||||
description: "Repository to clone, as a git URL, host/path reference, or GitHub owner/repo shorthand",
|
||||
}),
|
||||
refresh: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "When true, fetches the latest remote state into the managed cache",
|
||||
}),
|
||||
branch: Schema.optional(Schema.String).annotate({
|
||||
description: "Branch or ref to clone and inspect",
|
||||
}),
|
||||
})
|
||||
|
||||
type Metadata = {
|
||||
repository: string
|
||||
host: string
|
||||
remote: string
|
||||
localPath: string
|
||||
status: "cached" | "cloned" | "refreshed"
|
||||
head?: string
|
||||
branch?: string
|
||||
}
|
||||
|
||||
export const RepoCloneTool = Tool.define<typeof Parameters, Metadata, RepositoryCache.Service>(
|
||||
"repo_clone",
|
||||
Effect.gen(function* () {
|
||||
const cache = yield* RepositoryCache.Service
|
||||
|
||||
return {
|
||||
description: DESCRIPTION,
|
||||
parameters: Parameters,
|
||||
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context<Metadata>) =>
|
||||
Effect.gen(function* () {
|
||||
const reference = yield* RepositoryCache.parseRemoteReference(params.repository)
|
||||
if (params.branch) yield* RepositoryCache.validateBranch(params.branch)
|
||||
|
||||
const repository = reference.label
|
||||
const remote = reference.remote
|
||||
const localPath = repositoryCachePath(reference)
|
||||
|
||||
yield* ctx.ask({
|
||||
permission: "repo_clone",
|
||||
patterns: [repository],
|
||||
always: [repository],
|
||||
metadata: {
|
||||
repository,
|
||||
remote,
|
||||
path: localPath,
|
||||
refresh: Boolean(params.refresh),
|
||||
branch: params.branch,
|
||||
},
|
||||
})
|
||||
|
||||
const result = yield* cache.ensure({ reference, refresh: params.refresh, branch: params.branch })
|
||||
return {
|
||||
title: repository,
|
||||
metadata: result,
|
||||
output: [
|
||||
`Repository ready: ${repository}`,
|
||||
`Status: ${result.status}`,
|
||||
`Local path: ${localPath}`,
|
||||
...(result.branch ? [`Branch: ${result.branch}`] : []),
|
||||
...(result.head ? [`HEAD: ${result.head}`] : []),
|
||||
].join("\n"),
|
||||
}
|
||||
}).pipe(
|
||||
Effect.catchIf(RepositoryCache.isError, (error) => Effect.fail(new Error(error.message))),
|
||||
Effect.orDie,
|
||||
),
|
||||
} satisfies Tool.DefWithoutID<typeof Parameters, Metadata>
|
||||
}),
|
||||
)
|
||||
@@ -1,5 +0,0 @@
|
||||
- Clone or refresh a repository into OpenCode's managed cache under the data directory
|
||||
- Accepts git URLs, forge host/path references, or GitHub owner/repo shorthand
|
||||
- Returns the cached absolute local path so other tools can explore the cloned source
|
||||
- Use this before Read, Glob, or Grep when the code you need lives outside the current workspace
|
||||
- This tool is intended for dependency and documentation research workflows, not for modifying the user's workspace
|
||||
@@ -1,279 +0,0 @@
|
||||
import path from "path"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { Git } from "@/git"
|
||||
import { assertExternalDirectoryEffect } from "./external-directory"
|
||||
import DESCRIPTION from "./repo_overview.txt"
|
||||
import * as Tool from "./tool"
|
||||
import { parseRepositoryReference, repositoryCachePath } from "@/util/repository"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
|
||||
export const Parameters = Schema.Struct({
|
||||
repository: Schema.optional(Schema.String).annotate({
|
||||
description: "Cached repository to inspect, as a git URL, host/path reference, or GitHub owner/repo shorthand",
|
||||
}),
|
||||
path: Schema.optional(Schema.String).annotate({
|
||||
description: "Directory path to inspect instead of a cached repository",
|
||||
}),
|
||||
depth: Schema.optional(Schema.Number).annotate({
|
||||
description: "Maximum structure depth to include. Defaults to 3.",
|
||||
}),
|
||||
})
|
||||
|
||||
type Metadata = {
|
||||
path: string
|
||||
repository?: string
|
||||
branch?: string
|
||||
head?: string
|
||||
package_manager?: string
|
||||
ecosystems: string[]
|
||||
dependency_files: string[]
|
||||
entrypoints: string[]
|
||||
depth: number
|
||||
truncated: boolean
|
||||
}
|
||||
|
||||
const IGNORED_DIRS = new Set([
|
||||
".git",
|
||||
"node_modules",
|
||||
"__pycache__",
|
||||
".venv",
|
||||
"dist",
|
||||
"build",
|
||||
".next",
|
||||
"target",
|
||||
"vendor",
|
||||
])
|
||||
const STRUCTURE_LIMIT = 200
|
||||
const DEPENDENCY_FILES = [
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"bun.lock",
|
||||
"bun.lockb",
|
||||
"pnpm-lock.yaml",
|
||||
"yarn.lock",
|
||||
"requirements.txt",
|
||||
"pyproject.toml",
|
||||
"go.mod",
|
||||
"Cargo.toml",
|
||||
"Gemfile",
|
||||
"build.gradle",
|
||||
"build.gradle.kts",
|
||||
"pom.xml",
|
||||
"composer.json",
|
||||
]
|
||||
|
||||
function packageManager(files: Set<string>) {
|
||||
if (files.has("bun.lock") || files.has("bun.lockb")) return "bun"
|
||||
if (files.has("pnpm-lock.yaml")) return "pnpm"
|
||||
if (files.has("yarn.lock")) return "yarn"
|
||||
if (files.has("package-lock.json")) return "npm"
|
||||
}
|
||||
|
||||
function ecosystems(files: Set<string>) {
|
||||
return [
|
||||
...(files.has("package.json") ? ["Node.js"] : []),
|
||||
...(files.has("pyproject.toml") || files.has("requirements.txt") ? ["Python"] : []),
|
||||
...(files.has("go.mod") ? ["Go"] : []),
|
||||
...(files.has("Cargo.toml") ? ["Rust"] : []),
|
||||
...(files.has("Gemfile") ? ["Ruby"] : []),
|
||||
...(files.has("build.gradle") || files.has("build.gradle.kts") || files.has("pom.xml") ? ["Java/Kotlin"] : []),
|
||||
...(files.has("composer.json") ? ["PHP"] : []),
|
||||
]
|
||||
}
|
||||
|
||||
function commonEntrypoints(files: Set<string>) {
|
||||
return [
|
||||
"index.ts",
|
||||
"index.tsx",
|
||||
"index.js",
|
||||
"index.mjs",
|
||||
"main.ts",
|
||||
"main.js",
|
||||
"src/index.ts",
|
||||
"src/index.tsx",
|
||||
"src/index.js",
|
||||
"src/main.ts",
|
||||
"src/main.js",
|
||||
].filter((file) => files.has(file))
|
||||
}
|
||||
|
||||
export const RepoOverviewTool = Tool.define<typeof Parameters, Metadata, AppFileSystem.Service | Git.Service>(
|
||||
"repo_overview",
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* AppFileSystem.Service
|
||||
const git = yield* Git.Service
|
||||
|
||||
const resolveTarget = Effect.fn("RepoOverviewTool.resolveTarget")(function* (
|
||||
params: Schema.Schema.Type<typeof Parameters>,
|
||||
) {
|
||||
if (params.path) {
|
||||
const full = path.isAbsolute(params.path)
|
||||
? params.path
|
||||
: path.resolve(yield* InstanceState.directory, params.path)
|
||||
return { path: full, repository: params.repository }
|
||||
}
|
||||
|
||||
if (!params.repository) throw new Error("Either repository or path is required")
|
||||
|
||||
const parsed = parseRepositoryReference(params.repository)
|
||||
if (!parsed) throw new Error("Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand")
|
||||
|
||||
const repository = parsed.label
|
||||
return {
|
||||
repository,
|
||||
path: repositoryCachePath(parsed),
|
||||
}
|
||||
})
|
||||
|
||||
const structure = Effect.fn("RepoOverviewTool.structure")(function* (root: string, depth: number) {
|
||||
let truncated = false
|
||||
const lines: string[] = []
|
||||
|
||||
const visit: (dir: string, level: number) => Effect.Effect<void> = Effect.fnUntraced(function* (
|
||||
dir: string,
|
||||
level: number,
|
||||
) {
|
||||
if (level >= depth || lines.length >= STRUCTURE_LIMIT) {
|
||||
truncated = truncated || lines.length >= STRUCTURE_LIMIT
|
||||
return
|
||||
}
|
||||
|
||||
const entries = yield* fs.readDirectoryEntries(dir).pipe(Effect.orElseSucceed(() => []))
|
||||
const sorted = yield* Effect.forEach(
|
||||
entries,
|
||||
Effect.fnUntraced(function* (entry) {
|
||||
if (IGNORED_DIRS.has(entry.name)) return undefined
|
||||
const full = path.join(dir, entry.name)
|
||||
const info = yield* fs.stat(full).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||
if (!info) return undefined
|
||||
return { name: entry.name, full, directory: info.type === "Directory" }
|
||||
}),
|
||||
{ concurrency: 16 },
|
||||
).pipe(
|
||||
Effect.map((items) =>
|
||||
items
|
||||
.filter((item): item is { name: string; full: string; directory: boolean } => Boolean(item))
|
||||
.sort((a, b) => Number(b.directory) - Number(a.directory) || a.name.localeCompare(b.name)),
|
||||
),
|
||||
)
|
||||
|
||||
for (const entry of sorted) {
|
||||
if (lines.length >= STRUCTURE_LIMIT) {
|
||||
truncated = true
|
||||
return
|
||||
}
|
||||
|
||||
lines.push(`${" ".repeat(level)}${entry.name}${entry.directory ? "/" : ""}`)
|
||||
if (entry.directory) yield* visit(entry.full, level + 1)
|
||||
}
|
||||
})
|
||||
|
||||
yield* visit(root, 0)
|
||||
return { lines, truncated }
|
||||
})
|
||||
|
||||
return {
|
||||
description: DESCRIPTION,
|
||||
parameters: Parameters,
|
||||
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context<Metadata>) =>
|
||||
Effect.gen(function* () {
|
||||
const target = yield* resolveTarget(params)
|
||||
const depth =
|
||||
!params.depth || !Number.isInteger(params.depth) || params.depth < 1 || params.depth > 6 ? 3 : params.depth
|
||||
|
||||
yield* assertExternalDirectoryEffect(ctx, target.path, { kind: "directory" })
|
||||
yield* ctx.ask({
|
||||
permission: "repo_overview",
|
||||
patterns: [target.repository ?? target.path],
|
||||
always: [target.repository ?? target.path],
|
||||
metadata: {
|
||||
repository: target.repository,
|
||||
path: target.path,
|
||||
depth,
|
||||
},
|
||||
})
|
||||
|
||||
const info = yield* fs.stat(target.path).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||
if (!info) {
|
||||
if (target.repository)
|
||||
throw new Error(`Repository is not cloned: ${target.repository}. Use repo_clone first.`)
|
||||
throw new Error(`Directory not found: ${target.path}`)
|
||||
}
|
||||
if (info.type !== "Directory") throw new Error(`Path is not a directory: ${target.path}`)
|
||||
|
||||
const entries = yield* fs.readDirectoryEntries(target.path).pipe(Effect.orElseSucceed(() => []))
|
||||
const topLevel = new Set(entries.map((entry) => entry.name))
|
||||
const dependencyFiles = DEPENDENCY_FILES.filter((file) => topLevel.has(file))
|
||||
const packageJson = topLevel.has("package.json")
|
||||
? ((yield* fs
|
||||
.readJson(path.join(target.path, "package.json"))
|
||||
.pipe(Effect.orElseSucceed(() => ({})))) as Record<string, unknown>)
|
||||
: {}
|
||||
|
||||
const entrypoints = [
|
||||
...(typeof packageJson.main === "string" ? [`main: ${packageJson.main}`] : []),
|
||||
...(typeof packageJson.module === "string" ? [`module: ${packageJson.module}`] : []),
|
||||
...(typeof packageJson.types === "string" ? [`types: ${packageJson.types}`] : []),
|
||||
...(typeof packageJson.bin === "string" ? [`bin: ${packageJson.bin}`] : []),
|
||||
...(packageJson.bin && typeof packageJson.bin === "object" && !Array.isArray(packageJson.bin)
|
||||
? Object.keys(packageJson.bin as Record<string, unknown>).map((name) => `bin: ${name}`)
|
||||
: []),
|
||||
...(packageJson.exports && typeof packageJson.exports === "object" && !Array.isArray(packageJson.exports)
|
||||
? Object.keys(packageJson.exports as Record<string, unknown>)
|
||||
.slice(0, 10)
|
||||
.map((name) => `exports: ${name}`)
|
||||
: []),
|
||||
]
|
||||
|
||||
const common = commonEntrypoints(
|
||||
new Set([
|
||||
...topLevel,
|
||||
...entries
|
||||
.filter((entry) => entry.name === "src")
|
||||
.flatMap(() => ["src/index.ts", "src/index.tsx", "src/index.js", "src/main.ts", "src/main.js"]),
|
||||
]),
|
||||
)
|
||||
const structureResult = yield* structure(target.path, depth)
|
||||
const branch = yield* git.branch(target.path)
|
||||
const head = yield* git.run(["rev-parse", "HEAD"], { cwd: target.path })
|
||||
const headText = head.exitCode === 0 ? head.text().trim() : undefined
|
||||
|
||||
const metadata: Metadata = {
|
||||
path: target.path,
|
||||
repository: target.repository,
|
||||
branch,
|
||||
head: headText,
|
||||
package_manager: packageManager(topLevel),
|
||||
ecosystems: ecosystems(topLevel),
|
||||
dependency_files: dependencyFiles,
|
||||
entrypoints: [...entrypoints, ...common.map((file) => `file: ${file}`)],
|
||||
depth,
|
||||
truncated: structureResult.truncated,
|
||||
}
|
||||
|
||||
return {
|
||||
title: target.repository ?? path.basename(target.path),
|
||||
metadata,
|
||||
output: [
|
||||
`Path: ${target.path}`,
|
||||
...(target.repository ? [`Repository: ${target.repository}`] : []),
|
||||
...(branch ? [`Branch: ${branch}`] : []),
|
||||
...(headText ? [`HEAD: ${headText}`] : []),
|
||||
...(metadata.ecosystems.length ? [`Ecosystems: ${metadata.ecosystems.join(", ")}`] : []),
|
||||
...(metadata.package_manager ? [`Package manager: ${metadata.package_manager}`] : []),
|
||||
...(metadata.dependency_files.length
|
||||
? [`Dependency files: ${metadata.dependency_files.join(", ")}`]
|
||||
: []),
|
||||
...(metadata.entrypoints.length
|
||||
? ["Likely entrypoints:", ...metadata.entrypoints.map((entry) => `- ${entry}`)]
|
||||
: []),
|
||||
"Top-level structure:",
|
||||
...structureResult.lines,
|
||||
...(structureResult.truncated ? ["(Structure truncated)"] : []),
|
||||
].join("\n"),
|
||||
}
|
||||
}).pipe(Effect.orDie),
|
||||
} satisfies Tool.DefWithoutID<typeof Parameters, Metadata>
|
||||
}),
|
||||
)
|
||||
@@ -1,4 +0,0 @@
|
||||
- Summarize the structure and likely entrypoints of a cloned repository or local directory
|
||||
- Accepts either a cached repository reference or a directory path
|
||||
- Reports detected ecosystems, dependency files, package manager, likely entrypoints, and a compact structure tree
|
||||
- Use this after repo_clone to orient quickly before deeper Read, Glob, or Grep investigation
|
||||
Reference in New Issue
Block a user