refactor(core): replace bash tool with shell tool
This commit is contained in:
parent
595c6bd4a7
commit
5ae93092aa
37 changed files with 2260 additions and 901 deletions
|
|
@ -9,7 +9,7 @@ import { PtyTicket } from "@opencode-ai/core/pty/ticket"
|
|||
import { LocationServiceMap, locationServiceMapLayer } from "@opencode-ai/core/location-services"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { Shell } from "@opencode-ai/core/shell"
|
||||
import { ShellSelect } from "@opencode-ai/core/shell/select"
|
||||
import { CorsConfig, isAllowedRequestOrigin, type CorsOptions } from "@opencode-ai/server/cors"
|
||||
import {
|
||||
PTY_CONNECT_TICKET_QUERY,
|
||||
|
|
@ -58,7 +58,7 @@ export const ptyHandlers = HttpApiBuilder.group(InstanceHttpApi, "pty", (handler
|
|||
})
|
||||
|
||||
const shells = Effect.fn("PtyHttpApi.shells")(function* () {
|
||||
return yield* Effect.promise(() => Shell.list())
|
||||
return yield* Effect.promise(() => ShellSelect.list())
|
||||
})
|
||||
|
||||
const list = Effect.fn("PtyHttpApi.list")(function* () {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import { Tool } from "@/tool/tool"
|
|||
import { Permission } from "@/permission"
|
||||
import { SessionStatus } from "./status"
|
||||
import { LLM } from "./llm"
|
||||
import { Shell } from "@opencode-ai/core/shell"
|
||||
import { ShellSelect } from "@opencode-ai/core/shell/select"
|
||||
import { ShellID } from "@/tool/shell/id"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Truncate } from "@/tool/truncate"
|
||||
|
|
@ -520,8 +520,8 @@ export const layer = Layer.effect(
|
|||
}).pipe(Effect.ensuring(markReady))
|
||||
|
||||
const cfg = yield* config.get()
|
||||
const sh = Shell.preferred(cfg.shell)
|
||||
const args = Shell.args(sh, input.command, cwd)
|
||||
const sh = ShellSelect.preferred(cfg.shell)
|
||||
const args = ShellSelect.args(sh, input.command, cwd)
|
||||
let output = ""
|
||||
let aborted = false
|
||||
|
||||
|
|
@ -1396,7 +1396,7 @@ export const layer = Layer.effect(
|
|||
const shellMatches = ConfigMarkdown.shell(template)
|
||||
if (shellMatches.length > 0) {
|
||||
const cfg = yield* config.get()
|
||||
const sh = Shell.preferred(cfg.shell)
|
||||
const sh = ShellSelect.preferred(cfg.shell)
|
||||
const results = yield* Effect.promise(() =>
|
||||
Promise.all(
|
||||
shellMatches.map(async ([, cmd]) => (await Process.text([cmd], { shell: sh, nothrow: true })).text),
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
|
|||
import { fileURLToPath } from "url"
|
||||
import { Config } from "@/config/config"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { Shell } from "@opencode-ai/core/shell"
|
||||
import { ShellSelect } from "@opencode-ai/core/shell/select"
|
||||
import { ShellID } from "./shell/id"
|
||||
|
||||
import * as Truncate from "./truncate"
|
||||
|
|
@ -291,7 +291,7 @@ const ask = Effect.fn("ShellTool.ask")(function* (ctx: Tool.Context, scan: Scan,
|
|||
})
|
||||
|
||||
function cmd(shell: string, command: string, cwd: string, env: NodeJS.ProcessEnv) {
|
||||
if (process.platform === "win32" && Shell.ps(shell)) {
|
||||
if (process.platform === "win32" && ShellSelect.ps(shell)) {
|
||||
return ChildProcess.make(shell, ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", command], {
|
||||
cwd,
|
||||
env,
|
||||
|
|
@ -357,7 +357,7 @@ export const ShellTool = Tool.define(
|
|||
|
||||
const resolvePath = Effect.fn("ShellTool.resolvePath")(function* (text: string, root: string, shell: string) {
|
||||
if (process.platform === "win32") {
|
||||
if (Shell.posix(shell) && text.startsWith("/") && FSUtil.windowsPath(text) === text) {
|
||||
if (ShellSelect.posix(shell) && text.startsWith("/") && FSUtil.windowsPath(text) === text) {
|
||||
const file = yield* cygpath(shell, text)
|
||||
if (file) return file
|
||||
}
|
||||
|
|
@ -387,7 +387,7 @@ export const ShellTool = Tool.define(
|
|||
patterns: new Set<string>(),
|
||||
always: new Set<string>(),
|
||||
}
|
||||
const shellKind = ShellID.toKind(Shell.name(shell))
|
||||
const shellKind = ShellID.toKind(ShellSelect.name(shell))
|
||||
|
||||
for (const node of commands(root)) {
|
||||
const command = parts(node)
|
||||
|
|
@ -597,8 +597,8 @@ export const ShellTool = Tool.define(
|
|||
return () =>
|
||||
Effect.gen(function* () {
|
||||
const cfg = yield* config.get()
|
||||
const shell = Shell.acceptable(cfg.shell)
|
||||
const name = Shell.name(shell)
|
||||
const shell = ShellSelect.acceptable(cfg.shell)
|
||||
const name = ShellSelect.name(shell)
|
||||
const limits = yield* trunc.limits()
|
||||
const prompt = ShellPrompt.render(name, process.platform, limits, defaultTimeoutMs)
|
||||
yield* Effect.logInfo("shell tool using shell", { shell })
|
||||
|
|
@ -616,7 +616,7 @@ export const ShellTool = Tool.define(
|
|||
throw new Error(`Invalid timeout value: ${params.timeout}. Timeout must be a positive number.`)
|
||||
}
|
||||
const timeout = params.timeout ?? defaultTimeoutMs
|
||||
const ps = Shell.ps(shell)
|
||||
const ps = ShellSelect.ps(shell)
|
||||
yield* Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const tree = yield* Effect.acquireRelease(parse(params.command, ps), (tree) =>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ import { SessionV2 } from "@opencode-ai/core/session"
|
|||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||
import { Skill } from "../../src/skill"
|
||||
import { SystemPrompt } from "../../src/session/system"
|
||||
import { Shell } from "@opencode-ai/core/shell"
|
||||
import { ShellSelect } from "@opencode-ai/core/shell/select"
|
||||
import { Snapshot } from "../../src/snapshot"
|
||||
import { ToolRegistry } from "@/tool/registry"
|
||||
import { Truncate } from "@/tool/truncate"
|
||||
|
|
@ -77,7 +77,7 @@ function withSh<A, E, R>(fx: () => Effect.Effect<A, E, R>) {
|
|||
Effect.sync(() => {
|
||||
const prev = process.env.SHELL
|
||||
process.env.SHELL = "/bin/sh"
|
||||
Shell.preferred.reset()
|
||||
ShellSelect.preferred.reset()
|
||||
return prev
|
||||
}),
|
||||
() => fx(),
|
||||
|
|
@ -85,7 +85,7 @@ function withSh<A, E, R>(fx: () => Effect.Effect<A, E, R>) {
|
|||
Effect.sync(() => {
|
||||
if (prev === undefined) delete process.env.SHELL
|
||||
else process.env.SHELL = prev
|
||||
Shell.preferred.reset()
|
||||
ShellSelect.preferred.reset()
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type * as Scope from "effect/Scope"
|
|||
import os from "os"
|
||||
import path from "path"
|
||||
import { Config } from "@/config/config"
|
||||
import { Shell } from "@opencode-ai/core/shell"
|
||||
import { ShellSelect } from "@opencode-ai/core/shell/select"
|
||||
import { ShellTool } from "../../src/tool/shell"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { provideInstance, testInstanceStoreLayer, tmpdirScoped } from "../fixture/fixture"
|
||||
|
|
@ -77,25 +77,25 @@ const ctx = {
|
|||
ask: () => Effect.void,
|
||||
}
|
||||
|
||||
Shell.acceptable.reset()
|
||||
ShellSelect.acceptable.reset()
|
||||
const quote = (text: string) => `"${text}"`
|
||||
const squote = (text: string) => `'${text}'`
|
||||
const projectRoot = path.join(__dirname, "../..")
|
||||
const bin = quote(process.execPath.replaceAll("\\", "/"))
|
||||
const bash = (() => {
|
||||
const shell = Shell.acceptable()
|
||||
if (Shell.name(shell) === "bash") return shell
|
||||
return Shell.gitbash()
|
||||
const shell = ShellSelect.acceptable()
|
||||
if (ShellSelect.name(shell) === "bash") return shell
|
||||
return ShellSelect.gitbash()
|
||||
})()
|
||||
const shells = (() => {
|
||||
if (process.platform !== "win32") {
|
||||
const shell = Shell.acceptable()
|
||||
return [{ label: Shell.name(shell), shell }]
|
||||
const shell = ShellSelect.acceptable()
|
||||
return [{ label: ShellSelect.name(shell), shell }]
|
||||
}
|
||||
|
||||
const list = [bash, Bun.which("pwsh"), Bun.which("powershell"), process.env.COMSPEC || Bun.which("cmd.exe")]
|
||||
.filter((shell): shell is string => Boolean(shell))
|
||||
.map((shell) => ({ label: Shell.name(shell), shell }))
|
||||
.map((shell) => ({ label: ShellSelect.name(shell), shell }))
|
||||
|
||||
return list.filter(
|
||||
(item, i) => list.findIndex((other) => other.shell.toLowerCase() === item.shell.toLowerCase()) === i,
|
||||
|
|
@ -105,7 +105,7 @@ const PS = new Set(["pwsh", "powershell"])
|
|||
const ps = shells.filter((item) => PS.has(item.label))
|
||||
const cmdShell = shells.find((item) => item.label === "cmd")
|
||||
|
||||
const sh = () => Shell.name(Shell.acceptable())
|
||||
const sh = () => ShellSelect.name(ShellSelect.acceptable())
|
||||
const evalarg = (text: string) => (sh() === "cmd" ? quote(text) : squote(text))
|
||||
|
||||
const fill = (mode: "lines" | "bytes", n: number) => {
|
||||
|
|
@ -133,8 +133,8 @@ const withShell = <A, E, R>(item: { label: string; shell: string }, self: Effect
|
|||
Effect.sync(() => {
|
||||
const prev = process.env.SHELL
|
||||
process.env.SHELL = item.shell
|
||||
Shell.acceptable.reset()
|
||||
Shell.preferred.reset()
|
||||
ShellSelect.acceptable.reset()
|
||||
ShellSelect.preferred.reset()
|
||||
return prev
|
||||
}),
|
||||
() => self,
|
||||
|
|
@ -142,8 +142,8 @@ const withShell = <A, E, R>(item: { label: string; shell: string }, self: Effect
|
|||
Effect.sync(() => {
|
||||
if (prev === undefined) delete process.env.SHELL
|
||||
else process.env.SHELL = prev
|
||||
Shell.acceptable.reset()
|
||||
Shell.preferred.reset()
|
||||
ShellSelect.acceptable.reset()
|
||||
ShellSelect.preferred.reset()
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
@ -196,7 +196,7 @@ describe("tool.shell", () => {
|
|||
tmp,
|
||||
Effect.gen(function* () {
|
||||
const bash = yield* initBash()
|
||||
const fallback = Shell.name(Shell.acceptable("fish"))
|
||||
const fallback = ShellSelect.name(ShellSelect.acceptable("fish"))
|
||||
expect(fallback).not.toBe("fish")
|
||||
expect(bash.description).toContain(fallback)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue