mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-08 01:52:33 +00:00
refactor(tool): yield InstanceState context (#25199)
This commit is contained in:
parent
5c2e06f353
commit
668d77bb4e
8 changed files with 51 additions and 36 deletions
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
? ""
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue