chore(opencode): remove scout agent (#30435)

This commit is contained in:
Shoubhit Dash
2026-06-02 23:30:34 +05:30
committed by GitHub
parent 18ba80f3ac
commit a639fe7a08
30 changed files with 26 additions and 925 deletions

View File

@@ -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),

View File

@@ -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>
}),
)

View File

@@ -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

View File

@@ -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>
}),
)

View File

@@ -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