refactor(tool): yield InstanceState context (#25199)

This commit is contained in:
Kit Langton 2026-04-30 21:01:06 -04:00 committed by GitHub
parent 5c2e06f353
commit 668d77bb4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 51 additions and 36 deletions

View file

@ -256,7 +256,8 @@ export const layer = Layer.effect(
const assistantMessage = input.messages.findLast((msg) => msg.info.role === "assistant")
if (input.agent.name !== "plan" && assistantMessage?.info.agent === "plan") {
const plan = Session.plan(input.session)
const ctx = yield* InstanceState.context
const plan = Session.plan(input.session, ctx)
if (!(yield* fsys.existsSafe(plan))) return input.messages
const part = yield* sessions.updatePart({
id: PartID.ascending(),
@ -272,7 +273,8 @@ export const layer = Layer.effect(
if (input.agent.name !== "plan" || assistantMessage?.info.agent === "plan") return input.messages
const plan = Session.plan(input.session)
const ctx = yield* InstanceState.context
const plan = Session.plan(input.session, ctx)
const exists = yield* fsys.existsSafe(plan)
if (!exists) yield* fsys.ensureDir(path.dirname(plan)).pipe(Effect.catch(Effect.die))
const part = yield* sessions.updatePart({

View file

@ -26,7 +26,7 @@ import { ProjectTable } from "../project/project.sql"
import { Storage } from "@/storage/storage"
import * as Log from "@opencode-ai/core/util/log"
import { MessageV2 } from "./message-v2"
import { Instance } from "../project/instance"
import { Instance, type InstanceContext } from "../project/instance"
import { InstanceState } from "@/effect/instance-state"
import { Snapshot } from "@/snapshot"
import { ProjectID } from "../project/schema"
@ -311,9 +311,9 @@ export const Event = {
),
}
export function plan(input: { slug: string; time: { created: number } }) {
const base = Instance.project.vcs
? path.join(Instance.worktree, ".opencode", "plans")
export function plan(input: { slug: string; time: { created: number } }, instance: InstanceContext) {
const base = instance.project.vcs
? path.join(instance.worktree, ".opencode", "plans")
: path.join(Global.Path.data, "plans")
return path.join(base, [input.time.created, input.slug].join("-") + ".md")
}

View file

@ -3,7 +3,7 @@ import { Effect, Schema } from "effect"
import * as Tool from "./tool"
import { Bus } from "../bus"
import { FileWatcher } from "../file/watcher"
import { Instance } from "../project/instance"
import { InstanceState } from "@/effect/instance-state"
import { Patch } from "../patch"
import { createTwoFilesPatch, diffLines } from "diff"
import { assertExternalDirectoryEffect } from "./external-directory"
@ -52,6 +52,8 @@ export const ApplyPatchTool = Tool.define(
return yield* Effect.fail(new Error("apply_patch verification failed: no hunks found"))
}
const instance = yield* InstanceState.context
// Validate file paths and check permissions
const fileChanges: Array<{
filePath: string
@ -68,7 +70,7 @@ export const ApplyPatchTool = Tool.define(
let totalDiff = ""
for (const hunk of hunks) {
const filePath = path.resolve(Instance.directory, hunk.path)
const filePath = path.resolve(instance.directory, hunk.path)
yield* assertExternalDirectoryEffect(ctx, filePath)
switch (hunk.type) {
@ -133,7 +135,7 @@ export const ApplyPatchTool = Tool.define(
if (change.removed) deletions += change.count || 0
}
const movePath = hunk.move_path ? path.resolve(Instance.directory, hunk.move_path) : undefined
const movePath = hunk.move_path ? path.resolve(instance.directory, hunk.move_path) : undefined
yield* assertExternalDirectoryEffect(ctx, movePath)
fileChanges.push({
@ -187,7 +189,7 @@ export const ApplyPatchTool = Tool.define(
// Build per-file metadata for UI rendering (used for both permission and result)
const files = fileChanges.map((change) => ({
filePath: change.filePath,
relativePath: path.relative(Instance.worktree, change.movePath ?? change.filePath).replaceAll("\\", "/"),
relativePath: path.relative(instance.worktree, change.movePath ?? change.filePath).replaceAll("\\", "/"),
type: change.type,
patch: change.diff,
additions: change.additions,
@ -196,7 +198,7 @@ export const ApplyPatchTool = Tool.define(
}))
// Check permissions if needed
const relativePaths = fileChanges.map((c) => path.relative(Instance.worktree, c.filePath).replaceAll("\\", "/"))
const relativePaths = fileChanges.map((c) => path.relative(instance.worktree, c.filePath).replaceAll("\\", "/"))
yield* ctx.ask({
permission: "edit",
patterns: relativePaths,
@ -267,13 +269,13 @@ export const ApplyPatchTool = Tool.define(
// Generate output summary
const summaryLines = fileChanges.map((change) => {
if (change.type === "add") {
return `A ${path.relative(Instance.worktree, change.filePath).replaceAll("\\", "/")}`
return `A ${path.relative(instance.worktree, change.filePath).replaceAll("\\", "/")}`
}
if (change.type === "delete") {
return `D ${path.relative(Instance.worktree, change.filePath).replaceAll("\\", "/")}`
return `D ${path.relative(instance.worktree, change.filePath).replaceAll("\\", "/")}`
}
const target = change.movePath ?? change.filePath
return `M ${path.relative(Instance.worktree, target).replaceAll("\\", "/")}`
return `M ${path.relative(instance.worktree, target).replaceAll("\\", "/")}`
})
let output = `Success. Updated the following files:\n${summaryLines.join("\n")}`
@ -282,7 +284,7 @@ export const ApplyPatchTool = Tool.define(
const target = change.movePath ?? change.filePath
const block = LSP.Diagnostic.report(target, diagnostics[AppFileSystem.normalizePath(target)] ?? [])
if (!block) continue
const rel = path.relative(Instance.worktree, target).replaceAll("\\", "/")
const rel = path.relative(instance.worktree, target).replaceAll("\\", "/")
output += `\n\nLSP errors detected in ${rel}, please fix:\n${block}`
}

View file

@ -6,7 +6,7 @@ import * as Tool from "./tool"
import path from "path"
import DESCRIPTION from "./bash.txt"
import * as Log from "@opencode-ai/core/util/log"
import { Instance } from "../project/instance"
import { Instance, type InstanceContext } from "../project/instance"
import { lazy } from "@/util/lazy"
import { Language, type Node } from "web-tree-sitter"
@ -363,7 +363,13 @@ export const BashTool = Tool.define(
return yield* resolvePath(next, cwd, shell)
})
const collect = Effect.fn("BashTool.collect")(function* (root: Node, cwd: string, ps: boolean, shell: string) {
const collect = Effect.fn("BashTool.collect")(function* (
root: Node,
cwd: string,
ps: boolean,
shell: string,
instance: InstanceContext,
) {
const scan: Scan = {
dirs: new Set<string>(),
patterns: new Set<string>(),
@ -379,7 +385,7 @@ export const BashTool = Tool.define(
for (const arg of pathArgs(command, ps)) {
const resolved = yield* argPath(arg, cwd, ps, shell)
log.info("resolved path", { arg, resolved })
if (!resolved || Instance.containsPath(resolved)) continue
if (!resolved || Instance.containsPath(resolved, instance)) continue
const dir = (yield* fs.isDir(resolved)) ? resolved : path.dirname(resolved)
scan.dirs.add(dir)
}
@ -589,9 +595,10 @@ export const BashTool = Tool.define(
parameters: Parameters,
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) =>
Effect.gen(function* () {
const executeInstance = yield* InstanceState.context
const cwd = params.workdir
? yield* resolvePath(params.workdir, Instance.directory, shell)
: Instance.directory
? yield* resolvePath(params.workdir, executeInstance.directory, shell)
: executeInstance.directory
if (params.timeout !== undefined && params.timeout < 0) {
throw new Error(`Invalid timeout value: ${params.timeout}. Timeout must be a positive number.`)
}
@ -602,8 +609,8 @@ export const BashTool = Tool.define(
const tree = yield* Effect.acquireRelease(parse(params.command, ps), (tree) =>
Effect.sync(() => tree.delete()),
)
const scan = yield* collect(tree.rootNode, cwd, ps, shell)
if (!Instance.containsPath(cwd)) scan.dirs.add(cwd)
const scan = yield* collect(tree.rootNode, cwd, ps, shell, executeInstance)
if (!Instance.containsPath(cwd, executeInstance)) scan.dirs.add(cwd)
yield* ask(ctx, scan)
}),
)

View file

@ -13,7 +13,7 @@ import { File } from "../file"
import { FileWatcher } from "../file/watcher"
import { Bus } from "../bus"
import { Format } from "../format"
import { Instance } from "../project/instance"
import { InstanceState } from "@/effect/instance-state"
import { Snapshot } from "@/snapshot"
import { assertExternalDirectoryEffect } from "./external-directory"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
@ -76,9 +76,10 @@ export const EditTool = Tool.define(
throw new Error("No changes to apply: oldString and newString are identical.")
}
const instance = yield* InstanceState.context
const filePath = path.isAbsolute(params.filePath)
? params.filePath
: path.join(Instance.directory, params.filePath)
: path.join(instance.directory, params.filePath)
yield* assertExternalDirectoryEffect(ctx, filePath)
let diff = ""
@ -96,7 +97,7 @@ export const EditTool = Tool.define(
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
yield* ctx.ask({
permission: "edit",
patterns: [path.relative(Instance.worktree, filePath)],
patterns: [path.relative(instance.worktree, filePath)],
always: ["*"],
metadata: {
filepath: filePath,
@ -139,7 +140,7 @@ export const EditTool = Tool.define(
)
yield* ctx.ask({
permission: "edit",
patterns: [path.relative(Instance.worktree, filePath)],
patterns: [path.relative(instance.worktree, filePath)],
always: ["*"],
metadata: {
filepath: filePath,
@ -201,7 +202,7 @@ export const EditTool = Tool.define(
diff,
filediff,
},
title: `${path.relative(Instance.worktree, filePath)}`,
title: `${path.relative(instance.worktree, filePath)}`,
output,
}
}),

View file

@ -3,7 +3,7 @@ import * as Tool from "./tool"
import path from "path"
import { LSP } from "@/lsp/lsp"
import DESCRIPTION from "./lsp.txt"
import { Instance } from "../project/instance"
import { InstanceState } from "@/effect/instance-state"
import { pathToFileURL } from "url"
import { assertExternalDirectoryEffect } from "./external-directory"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
@ -44,7 +44,8 @@ export const LspTool = Tool.define(
parameters: Parameters,
execute: (args: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) =>
Effect.gen(function* () {
const file = path.isAbsolute(args.filePath) ? args.filePath : path.join(Instance.directory, args.filePath)
const instance = yield* InstanceState.context
const file = path.isAbsolute(args.filePath) ? args.filePath : path.join(instance.directory, args.filePath)
yield* assertExternalDirectoryEffect(ctx, file)
const meta =
args.operation === "workspaceSymbol"
@ -61,7 +62,7 @@ export const LspTool = Tool.define(
const uri = pathToFileURL(file).href
const position = { file, line: args.line - 1, character: args.character - 1 }
const relPath = path.relative(Instance.worktree, file)
const relPath = path.relative(instance.worktree, file)
const detail =
args.operation === "workspaceSymbol"
? ""

View file

@ -5,7 +5,7 @@ import { Question } from "../question"
import { Session } from "@/session/session"
import { MessageV2 } from "../session/message-v2"
import { Provider } from "@/provider/provider"
import { Instance } from "../project/instance"
import { InstanceState } from "@/effect/instance-state"
import { type SessionID, MessageID, PartID } from "../session/schema"
import EXIT_DESCRIPTION from "./plan-exit.txt"
@ -30,8 +30,9 @@ export const PlanExitTool = Tool.define(
parameters: Parameters,
execute: (_params: {}, ctx: Tool.Context) =>
Effect.gen(function* () {
const instance = yield* InstanceState.context
const info = yield* session.get(ctx.sessionID)
const plan = path.relative(Instance.worktree, Session.plan(info))
const plan = path.relative(instance.worktree, Session.plan(info, instance))
const answers = yield* question.ask({
sessionID: ctx.sessionID,
questions: [

View file

@ -10,7 +10,7 @@ import { File } from "../file"
import { FileWatcher } from "../file/watcher"
import { Format } from "../format"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { Instance } from "../project/instance"
import { InstanceState } from "@/effect/instance-state"
import { trimDiff } from "./edit"
import { assertExternalDirectoryEffect } from "./external-directory"
import * as Bom from "@/util/bom"
@ -37,9 +37,10 @@ export const WriteTool = Tool.define(
parameters: Parameters,
execute: (params: { content: string; filePath: string }, ctx: Tool.Context) =>
Effect.gen(function* () {
const instance = yield* InstanceState.context
const filepath = path.isAbsolute(params.filePath)
? params.filePath
: path.join(Instance.directory, params.filePath)
: path.join(instance.directory, params.filePath)
yield* assertExternalDirectoryEffect(ctx, filepath)
const exists = yield* fs.existsSafe(filepath)
@ -52,7 +53,7 @@ export const WriteTool = Tool.define(
const diff = trimDiff(createTwoFilesPatch(filepath, filepath, contentOld, contentNew))
yield* ctx.ask({
permission: "edit",
patterns: [path.relative(Instance.worktree, filepath)],
patterns: [path.relative(instance.worktree, filepath)],
always: ["*"],
metadata: {
filepath,
@ -89,7 +90,7 @@ export const WriteTool = Tool.define(
}
return {
title: path.relative(Instance.worktree, filepath),
title: path.relative(instance.worktree, filepath),
metadata: {
diagnostics,
filepath,