refactor(tool): convert bash to defineEffect with ChildProcessSpawner (#21895)
This commit is contained in:
@@ -8,7 +8,7 @@ import { WorkspaceContext } from "@/control-plane/workspace-context"
|
|||||||
|
|
||||||
export const memoMap = Layer.makeMemoMapUnsafe()
|
export const memoMap = Layer.makeMemoMapUnsafe()
|
||||||
|
|
||||||
function attach<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> {
|
export function attach<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> {
|
||||||
try {
|
try {
|
||||||
const ctx = Instance.current
|
const ctx = Instance.current
|
||||||
const workspaceID = WorkspaceContext.workspaceID
|
const workspaceID = WorkspaceContext.workspaceID
|
||||||
|
|||||||
+144
-134
@@ -8,8 +8,7 @@ import { Instance } from "../project/instance"
|
|||||||
import { lazy } from "@/util/lazy"
|
import { lazy } from "@/util/lazy"
|
||||||
import { Language, type Node } from "web-tree-sitter"
|
import { Language, type Node } from "web-tree-sitter"
|
||||||
|
|
||||||
import { Filesystem } from "@/util/filesystem"
|
import { AppFileSystem } from "@/filesystem"
|
||||||
import { Process } from "@/util/process"
|
|
||||||
import { fileURLToPath } from "url"
|
import { fileURLToPath } from "url"
|
||||||
import { Flag } from "@/flag/flag"
|
import { Flag } from "@/flag/flag"
|
||||||
import { Shell } from "@/shell/shell"
|
import { Shell } from "@/shell/shell"
|
||||||
@@ -17,9 +16,9 @@ import { Shell } from "@/shell/shell"
|
|||||||
import { BashArity } from "@/permission/arity"
|
import { BashArity } from "@/permission/arity"
|
||||||
import { Truncate } from "./truncate"
|
import { Truncate } from "./truncate"
|
||||||
import { Plugin } from "@/plugin"
|
import { Plugin } from "@/plugin"
|
||||||
import { Cause, Effect, Exit, Stream } from "effect"
|
import { Effect, Stream } from "effect"
|
||||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
import { ChildProcess } from "effect/unstable/process"
|
||||||
import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"
|
import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
|
||||||
|
|
||||||
const MAX_METADATA_LENGTH = 30_000
|
const MAX_METADATA_LENGTH = 30_000
|
||||||
const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS || 2 * 60 * 1000
|
const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS || 2 * 60 * 1000
|
||||||
@@ -183,33 +182,6 @@ function prefix(text: string) {
|
|||||||
return text.slice(0, match.index)
|
return text.slice(0, match.index)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cygpath(shell: string, text: string) {
|
|
||||||
const out = await Process.text([shell, "-lc", 'cygpath -w -- "$1"', "_", text], { nothrow: true })
|
|
||||||
if (out.code !== 0) return
|
|
||||||
const file = out.text.trim()
|
|
||||||
if (!file) return
|
|
||||||
return Filesystem.normalizePath(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolvePath(text: string, root: string, shell: string) {
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
if (Shell.posix(shell) && text.startsWith("/") && Filesystem.windowsPath(text) === text) {
|
|
||||||
const file = await cygpath(shell, text)
|
|
||||||
if (file) return file
|
|
||||||
}
|
|
||||||
return Filesystem.normalizePath(path.resolve(root, Filesystem.windowsPath(text)))
|
|
||||||
}
|
|
||||||
return path.resolve(root, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function argPath(arg: string, cwd: string, ps: boolean, shell: string) {
|
|
||||||
const text = ps ? expand(arg, cwd, shell) : home(unquote(arg))
|
|
||||||
const file = text && prefix(text)
|
|
||||||
if (!file || dynamic(file, ps)) return
|
|
||||||
const next = ps ? provider(file) : file
|
|
||||||
if (!next) return
|
|
||||||
return resolvePath(next, cwd, shell)
|
|
||||||
}
|
|
||||||
|
|
||||||
function pathArgs(list: Part[], ps: boolean) {
|
function pathArgs(list: Part[], ps: boolean) {
|
||||||
if (!ps) {
|
if (!ps) {
|
||||||
@@ -238,78 +210,45 @@ function pathArgs(list: Part[], ps: boolean) {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
async function collect(root: Node, cwd: string, ps: boolean, shell: string): Promise<Scan> {
|
|
||||||
const scan: Scan = {
|
|
||||||
dirs: new Set<string>(),
|
|
||||||
patterns: new Set<string>(),
|
|
||||||
always: new Set<string>(),
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const node of commands(root)) {
|
|
||||||
const command = parts(node)
|
|
||||||
const tokens = command.map((item) => item.text)
|
|
||||||
const cmd = ps ? tokens[0]?.toLowerCase() : tokens[0]
|
|
||||||
|
|
||||||
if (cmd && FILES.has(cmd)) {
|
|
||||||
for (const arg of pathArgs(command, ps)) {
|
|
||||||
const resolved = await argPath(arg, cwd, ps, shell)
|
|
||||||
log.info("resolved path", { arg, resolved })
|
|
||||||
if (!resolved || Instance.containsPath(resolved)) continue
|
|
||||||
const dir = (await Filesystem.isDir(resolved)) ? resolved : path.dirname(resolved)
|
|
||||||
scan.dirs.add(dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokens.length && (!cmd || !CWD.has(cmd))) {
|
|
||||||
scan.patterns.add(source(node))
|
|
||||||
scan.always.add(BashArity.prefix(tokens).join(" ") + " *")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return scan
|
|
||||||
}
|
|
||||||
|
|
||||||
function preview(text: string) {
|
function preview(text: string) {
|
||||||
if (text.length <= MAX_METADATA_LENGTH) return text
|
if (text.length <= MAX_METADATA_LENGTH) return text
|
||||||
return text.slice(0, MAX_METADATA_LENGTH) + "\n\n..."
|
return text.slice(0, MAX_METADATA_LENGTH) + "\n\n..."
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parse(command: string, ps: boolean) {
|
const parse = Effect.fn("BashTool.parse")(function* (command: string, ps: boolean) {
|
||||||
const tree = await parser().then((p) => (ps ? p.ps : p.bash).parse(command))
|
const tree = yield* Effect.promise(() => parser().then((p) => (ps ? p.ps : p.bash).parse(command)))
|
||||||
if (!tree) throw new Error("Failed to parse command")
|
if (!tree) throw new Error("Failed to parse command")
|
||||||
return tree.rootNode
|
return tree.rootNode
|
||||||
}
|
})
|
||||||
|
|
||||||
async function ask(ctx: Tool.Context, scan: Scan) {
|
const ask = Effect.fn("BashTool.ask")(function* (ctx: Tool.Context, scan: Scan) {
|
||||||
if (scan.dirs.size > 0) {
|
if (scan.dirs.size > 0) {
|
||||||
const globs = Array.from(scan.dirs).map((dir) => {
|
const globs = Array.from(scan.dirs).map((dir) => {
|
||||||
if (process.platform === "win32") return Filesystem.normalizePathPattern(path.join(dir, "*"))
|
if (process.platform === "win32") return AppFileSystem.normalizePathPattern(path.join(dir, "*"))
|
||||||
return path.join(dir, "*")
|
return path.join(dir, "*")
|
||||||
})
|
})
|
||||||
await ctx.ask({
|
yield* Effect.promise(() =>
|
||||||
|
ctx.ask({
|
||||||
permission: "external_directory",
|
permission: "external_directory",
|
||||||
patterns: globs,
|
patterns: globs,
|
||||||
always: globs,
|
always: globs,
|
||||||
metadata: {},
|
metadata: {},
|
||||||
})
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scan.patterns.size === 0) return
|
if (scan.patterns.size === 0) return
|
||||||
await ctx.ask({
|
yield* Effect.promise(() =>
|
||||||
|
ctx.ask({
|
||||||
permission: "bash",
|
permission: "bash",
|
||||||
patterns: Array.from(scan.patterns),
|
patterns: Array.from(scan.patterns),
|
||||||
always: Array.from(scan.always),
|
always: Array.from(scan.always),
|
||||||
metadata: {},
|
metadata: {},
|
||||||
})
|
}),
|
||||||
}
|
)
|
||||||
|
})
|
||||||
|
|
||||||
async function shellEnv(ctx: Tool.Context, cwd: string) {
|
|
||||||
const extra = await Plugin.trigger("shell.env", { cwd, sessionID: ctx.sessionID, callID: ctx.callID }, { env: {} })
|
|
||||||
return {
|
|
||||||
...process.env,
|
|
||||||
...extra.env,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cmd(shell: string, name: string, command: string, cwd: string, env: NodeJS.ProcessEnv) {
|
function cmd(shell: string, name: string, command: string, cwd: string, env: NodeJS.ProcessEnv) {
|
||||||
if (process.platform === "win32" && PS.has(name)) {
|
if (process.platform === "win32" && PS.has(name)) {
|
||||||
@@ -330,7 +269,111 @@ function cmd(shell: string, name: string, command: string, cwd: string, env: Nod
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function run(
|
|
||||||
|
const parser = lazy(async () => {
|
||||||
|
const { Parser } = await import("web-tree-sitter")
|
||||||
|
const { default: treeWasm } = await import("web-tree-sitter/tree-sitter.wasm" as string, {
|
||||||
|
with: { type: "wasm" },
|
||||||
|
})
|
||||||
|
const treePath = resolveWasm(treeWasm)
|
||||||
|
await Parser.init({
|
||||||
|
locateFile() {
|
||||||
|
return treePath
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const { default: bashWasm } = await import("tree-sitter-bash/tree-sitter-bash.wasm" as string, {
|
||||||
|
with: { type: "wasm" },
|
||||||
|
})
|
||||||
|
const { default: psWasm } = await import("tree-sitter-powershell/tree-sitter-powershell.wasm" as string, {
|
||||||
|
with: { type: "wasm" },
|
||||||
|
})
|
||||||
|
const bashPath = resolveWasm(bashWasm)
|
||||||
|
const psPath = resolveWasm(psWasm)
|
||||||
|
const [bashLanguage, psLanguage] = await Promise.all([Language.load(bashPath), Language.load(psPath)])
|
||||||
|
const bash = new Parser()
|
||||||
|
bash.setLanguage(bashLanguage)
|
||||||
|
const ps = new Parser()
|
||||||
|
ps.setLanguage(psLanguage)
|
||||||
|
return { bash, ps }
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: we may wanna rename this tool so it works better on other shells
|
||||||
|
export const BashTool = Tool.defineEffect(
|
||||||
|
"bash",
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const spawner = yield* ChildProcessSpawner
|
||||||
|
const fs = yield* AppFileSystem.Service
|
||||||
|
const plugin = yield* Plugin.Service
|
||||||
|
|
||||||
|
const cygpath = Effect.fn("BashTool.cygpath")(function* (shell: string, text: string) {
|
||||||
|
const lines = yield* spawner.lines(
|
||||||
|
ChildProcess.make(shell, ["-lc", 'cygpath -w -- "$1"', "_", text]),
|
||||||
|
).pipe(Effect.catch(() => Effect.succeed([] as string[])))
|
||||||
|
const file = lines[0]?.trim()
|
||||||
|
if (!file) return
|
||||||
|
return AppFileSystem.normalizePath(file)
|
||||||
|
})
|
||||||
|
|
||||||
|
const resolvePath = Effect.fn("BashTool.resolvePath")(function* (text: string, root: string, shell: string) {
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
if (Shell.posix(shell) && text.startsWith("/") && AppFileSystem.windowsPath(text) === text) {
|
||||||
|
const file = yield* cygpath(shell, text)
|
||||||
|
if (file) return file
|
||||||
|
}
|
||||||
|
return AppFileSystem.normalizePath(path.resolve(root, AppFileSystem.windowsPath(text)))
|
||||||
|
}
|
||||||
|
return path.resolve(root, text)
|
||||||
|
})
|
||||||
|
|
||||||
|
const argPath = Effect.fn("BashTool.argPath")(function* (arg: string, cwd: string, ps: boolean, shell: string) {
|
||||||
|
const text = ps ? expand(arg, cwd, shell) : home(unquote(arg))
|
||||||
|
const file = text && prefix(text)
|
||||||
|
if (!file || dynamic(file, ps)) return
|
||||||
|
const next = ps ? provider(file) : file
|
||||||
|
if (!next) return
|
||||||
|
return yield* resolvePath(next, cwd, shell)
|
||||||
|
})
|
||||||
|
|
||||||
|
const collect = Effect.fn("BashTool.collect")(function* (root: Node, cwd: string, ps: boolean, shell: string) {
|
||||||
|
const scan: Scan = {
|
||||||
|
dirs: new Set<string>(),
|
||||||
|
patterns: new Set<string>(),
|
||||||
|
always: new Set<string>(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const node of commands(root)) {
|
||||||
|
const command = parts(node)
|
||||||
|
const tokens = command.map((item) => item.text)
|
||||||
|
const cmd = ps ? tokens[0]?.toLowerCase() : tokens[0]
|
||||||
|
|
||||||
|
if (cmd && FILES.has(cmd)) {
|
||||||
|
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
|
||||||
|
const dir = (yield* fs.isDir(resolved)) ? resolved : path.dirname(resolved)
|
||||||
|
scan.dirs.add(dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokens.length && (!cmd || !CWD.has(cmd))) {
|
||||||
|
scan.patterns.add(source(node))
|
||||||
|
scan.always.add(BashArity.prefix(tokens).join(" ") + " *")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scan
|
||||||
|
})
|
||||||
|
|
||||||
|
const shellEnv = Effect.fn("BashTool.shellEnv")(function* (ctx: Tool.Context, cwd: string) {
|
||||||
|
const extra = yield* plugin.trigger("shell.env", { cwd, sessionID: ctx.sessionID, callID: ctx.callID }, { env: {} })
|
||||||
|
return {
|
||||||
|
...process.env,
|
||||||
|
...extra.env,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const run = Effect.fn("BashTool.run")(function* (
|
||||||
input: {
|
input: {
|
||||||
shell: string
|
shell: string
|
||||||
name: string
|
name: string
|
||||||
@@ -341,7 +384,7 @@ async function run(
|
|||||||
description: string
|
description: string
|
||||||
},
|
},
|
||||||
ctx: Tool.Context,
|
ctx: Tool.Context,
|
||||||
) {
|
) {
|
||||||
let output = ""
|
let output = ""
|
||||||
let expired = false
|
let expired = false
|
||||||
let aborted = false
|
let aborted = false
|
||||||
@@ -353,7 +396,7 @@ async function run(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const exit = await CrossSpawnSpawner.runPromiseExit((spawner) =>
|
const code: number | null = yield* Effect.scoped(
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const handle = yield* spawner.spawn(cmd(input.shell, input.name, input.command, input.cwd, input.env))
|
const handle = yield* spawner.spawn(cmd(input.shell, input.name, input.command, input.cwd, input.env))
|
||||||
|
|
||||||
@@ -396,15 +439,8 @@ async function run(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return exit.kind === "exit" ? exit.code : null
|
return exit.kind === "exit" ? exit.code : null
|
||||||
}).pipe(Effect.scoped, Effect.orDie),
|
}),
|
||||||
)
|
).pipe(Effect.orDie)
|
||||||
|
|
||||||
let code: number | null = null
|
|
||||||
if (Exit.isSuccess(exit)) {
|
|
||||||
code = exit.value
|
|
||||||
} else if (!Cause.hasInterruptsOnly(exit.cause)) {
|
|
||||||
throw Cause.squash(exit.cause)
|
|
||||||
}
|
|
||||||
|
|
||||||
const meta: string[] = []
|
const meta: string[] = []
|
||||||
if (expired) meta.push(`bash tool terminated command after exceeding timeout ${input.timeout} ms`)
|
if (expired) meta.push(`bash tool terminated command after exceeding timeout ${input.timeout} ms`)
|
||||||
@@ -422,37 +458,9 @@ async function run(
|
|||||||
},
|
},
|
||||||
output,
|
output,
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
const parser = lazy(async () => {
|
return async () => {
|
||||||
const { Parser } = await import("web-tree-sitter")
|
|
||||||
const { default: treeWasm } = await import("web-tree-sitter/tree-sitter.wasm" as string, {
|
|
||||||
with: { type: "wasm" },
|
|
||||||
})
|
|
||||||
const treePath = resolveWasm(treeWasm)
|
|
||||||
await Parser.init({
|
|
||||||
locateFile() {
|
|
||||||
return treePath
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const { default: bashWasm } = await import("tree-sitter-bash/tree-sitter-bash.wasm" as string, {
|
|
||||||
with: { type: "wasm" },
|
|
||||||
})
|
|
||||||
const { default: psWasm } = await import("tree-sitter-powershell/tree-sitter-powershell.wasm" as string, {
|
|
||||||
with: { type: "wasm" },
|
|
||||||
})
|
|
||||||
const bashPath = resolveWasm(bashWasm)
|
|
||||||
const psPath = resolveWasm(psWasm)
|
|
||||||
const [bashLanguage, psLanguage] = await Promise.all([Language.load(bashPath), Language.load(psPath)])
|
|
||||||
const bash = new Parser()
|
|
||||||
bash.setLanguage(bashLanguage)
|
|
||||||
const ps = new Parser()
|
|
||||||
ps.setLanguage(psLanguage)
|
|
||||||
return { bash, ps }
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: we may wanna rename this tool so it works better on other shells
|
|
||||||
export const BashTool = Tool.define("bash", async () => {
|
|
||||||
const shell = Shell.acceptable()
|
const shell = Shell.acceptable()
|
||||||
const name = Shell.name(shell)
|
const name = Shell.name(shell)
|
||||||
const chain =
|
const chain =
|
||||||
@@ -469,30 +477,32 @@ export const BashTool = Tool.define("bash", async () => {
|
|||||||
.replaceAll("${maxLines}", String(Truncate.MAX_LINES))
|
.replaceAll("${maxLines}", String(Truncate.MAX_LINES))
|
||||||
.replaceAll("${maxBytes}", String(Truncate.MAX_BYTES)),
|
.replaceAll("${maxBytes}", String(Truncate.MAX_BYTES)),
|
||||||
parameters: Parameters,
|
parameters: Parameters,
|
||||||
async execute(params, ctx) {
|
execute: (params: z.infer<typeof Parameters>, ctx: Tool.Context) =>
|
||||||
const cwd = params.workdir ? await resolvePath(params.workdir, Instance.directory, shell) : Instance.directory
|
Effect.gen(function* () {
|
||||||
|
const cwd = params.workdir
|
||||||
|
? yield* resolvePath(params.workdir, Instance.directory, shell)
|
||||||
|
: Instance.directory
|
||||||
if (params.timeout !== undefined && params.timeout < 0) {
|
if (params.timeout !== undefined && params.timeout < 0) {
|
||||||
throw new Error(`Invalid timeout value: ${params.timeout}. Timeout must be a positive number.`)
|
throw new Error(`Invalid timeout value: ${params.timeout}. Timeout must be a positive number.`)
|
||||||
}
|
}
|
||||||
const timeout = params.timeout ?? DEFAULT_TIMEOUT
|
const timeout = params.timeout ?? DEFAULT_TIMEOUT
|
||||||
const ps = PS.has(name)
|
const ps = PS.has(name)
|
||||||
const root = await parse(params.command, ps)
|
const root = yield* parse(params.command, ps)
|
||||||
const scan = await collect(root, cwd, ps, shell)
|
const scan = yield* collect(root, cwd, ps, shell)
|
||||||
if (!Instance.containsPath(cwd)) scan.dirs.add(cwd)
|
if (!Instance.containsPath(cwd)) scan.dirs.add(cwd)
|
||||||
await ask(ctx, scan)
|
yield* ask(ctx, scan)
|
||||||
|
|
||||||
return run(
|
return yield* run({
|
||||||
{
|
|
||||||
shell,
|
shell,
|
||||||
name,
|
name,
|
||||||
command: params.command,
|
command: params.command,
|
||||||
cwd,
|
cwd,
|
||||||
env: await shellEnv(ctx, cwd),
|
env: yield* shellEnv(ctx, cwd),
|
||||||
timeout,
|
timeout,
|
||||||
description: params.description,
|
description: params.description,
|
||||||
},
|
}, ctx)
|
||||||
ctx,
|
}).pipe(Effect.orDie, Effect.runPromise),
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import path from "path"
|
|||||||
import { pathToFileURL } from "url"
|
import { pathToFileURL } from "url"
|
||||||
import { Effect, Layer, ServiceMap } from "effect"
|
import { Effect, Layer, ServiceMap } from "effect"
|
||||||
import { FetchHttpClient, HttpClient } from "effect/unstable/http"
|
import { FetchHttpClient, HttpClient } from "effect/unstable/http"
|
||||||
|
import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
|
||||||
|
import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { makeRuntime } from "@/effect/run-service"
|
import { makeRuntime } from "@/effect/run-service"
|
||||||
import { Env } from "../env"
|
import { Env } from "../env"
|
||||||
@@ -86,6 +88,7 @@ export namespace ToolRegistry {
|
|||||||
| Instruction.Service
|
| Instruction.Service
|
||||||
| AppFileSystem.Service
|
| AppFileSystem.Service
|
||||||
| HttpClient.HttpClient
|
| HttpClient.HttpClient
|
||||||
|
| ChildProcessSpawner
|
||||||
> = Layer.effect(
|
> = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
@@ -102,6 +105,7 @@ export namespace ToolRegistry {
|
|||||||
const plan = yield* PlanExitTool
|
const plan = yield* PlanExitTool
|
||||||
const webfetch = yield* WebFetchTool
|
const webfetch = yield* WebFetchTool
|
||||||
const websearch = yield* WebSearchTool
|
const websearch = yield* WebSearchTool
|
||||||
|
const bash = yield* BashTool
|
||||||
const codesearch = yield* CodeSearchTool
|
const codesearch = yield* CodeSearchTool
|
||||||
|
|
||||||
const state = yield* InstanceState.make<State>(
|
const state = yield* InstanceState.make<State>(
|
||||||
@@ -161,7 +165,7 @@ export namespace ToolRegistry {
|
|||||||
|
|
||||||
const tool = yield* Effect.all({
|
const tool = yield* Effect.all({
|
||||||
invalid: Tool.init(InvalidTool),
|
invalid: Tool.init(InvalidTool),
|
||||||
bash: Tool.init(BashTool),
|
bash: Tool.init(bash),
|
||||||
read: Tool.init(read),
|
read: Tool.init(read),
|
||||||
glob: Tool.init(GlobTool),
|
glob: Tool.init(GlobTool),
|
||||||
grep: Tool.init(GrepTool),
|
grep: Tool.init(GrepTool),
|
||||||
@@ -315,6 +319,7 @@ export namespace ToolRegistry {
|
|||||||
Layer.provide(Instruction.defaultLayer),
|
Layer.provide(Instruction.defaultLayer),
|
||||||
Layer.provide(AppFileSystem.defaultLayer),
|
Layer.provide(AppFileSystem.defaultLayer),
|
||||||
Layer.provide(FetchHttpClient.layer),
|
Layer.provide(FetchHttpClient.layer),
|
||||||
|
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ function makeHttp() {
|
|||||||
const registry = ToolRegistry.layer.pipe(
|
const registry = ToolRegistry.layer.pipe(
|
||||||
Layer.provide(Skill.defaultLayer),
|
Layer.provide(Skill.defaultLayer),
|
||||||
Layer.provide(FetchHttpClient.layer),
|
Layer.provide(FetchHttpClient.layer),
|
||||||
|
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||||
Layer.provideMerge(todo),
|
Layer.provideMerge(todo),
|
||||||
Layer.provideMerge(question),
|
Layer.provideMerge(question),
|
||||||
Layer.provideMerge(deps),
|
Layer.provideMerge(deps),
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ function makeHttp() {
|
|||||||
const registry = ToolRegistry.layer.pipe(
|
const registry = ToolRegistry.layer.pipe(
|
||||||
Layer.provide(Skill.defaultLayer),
|
Layer.provide(Skill.defaultLayer),
|
||||||
Layer.provide(FetchHttpClient.layer),
|
Layer.provide(FetchHttpClient.layer),
|
||||||
|
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||||
Layer.provideMerge(todo),
|
Layer.provideMerge(todo),
|
||||||
Layer.provideMerge(question),
|
Layer.provideMerge(question),
|
||||||
Layer.provideMerge(deps),
|
Layer.provideMerge(deps),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import { Effect, Layer, ManagedRuntime } from "effect"
|
||||||
import os from "os"
|
import os from "os"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Shell } from "../../src/shell/shell"
|
import { Shell } from "../../src/shell/shell"
|
||||||
@@ -9,6 +10,19 @@ import { tmpdir } from "../fixture/fixture"
|
|||||||
import type { Permission } from "../../src/permission"
|
import type { Permission } from "../../src/permission"
|
||||||
import { Truncate } from "../../src/tool/truncate"
|
import { Truncate } from "../../src/tool/truncate"
|
||||||
import { SessionID, MessageID } from "../../src/session/schema"
|
import { SessionID, MessageID } from "../../src/session/schema"
|
||||||
|
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
||||||
|
import { AppFileSystem } from "../../src/filesystem"
|
||||||
|
import { Plugin } from "../../src/plugin"
|
||||||
|
|
||||||
|
const runtime = ManagedRuntime.make(
|
||||||
|
Layer.mergeAll(CrossSpawnSpawner.defaultLayer, AppFileSystem.defaultLayer, Plugin.defaultLayer),
|
||||||
|
)
|
||||||
|
|
||||||
|
function initBash() {
|
||||||
|
return runtime.runPromise(
|
||||||
|
BashTool.pipe(Effect.flatMap((info) => Effect.promise(() => info.init()))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const ctx = {
|
const ctx = {
|
||||||
sessionID: SessionID.make("ses_test"),
|
sessionID: SessionID.make("ses_test"),
|
||||||
@@ -118,7 +132,7 @@ describe("tool.bash", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const result = await bash.execute(
|
const result = await bash.execute(
|
||||||
{
|
{
|
||||||
command: "echo test",
|
command: "echo test",
|
||||||
@@ -139,7 +153,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await bash.execute(
|
await bash.execute(
|
||||||
{
|
{
|
||||||
@@ -160,7 +174,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await bash.execute(
|
await bash.execute(
|
||||||
{
|
{
|
||||||
@@ -184,7 +198,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await bash.execute(
|
await bash.execute(
|
||||||
{
|
{
|
||||||
@@ -208,7 +222,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
const file = process.platform === "win32" ? `${process.env.WINDIR!.replaceAll("\\", "/")}/*` : "/etc/*"
|
const file = process.platform === "win32" ? `${process.env.WINDIR!.replaceAll("\\", "/")}/*` : "/etc/*"
|
||||||
@@ -242,7 +256,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const file = path.join(outerTmp.path, "outside.txt").replaceAll("\\", "/")
|
const file = path.join(outerTmp.path, "outside.txt").replaceAll("\\", "/")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await bash.execute(
|
await bash.execute(
|
||||||
@@ -273,7 +287,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await expect(
|
await expect(
|
||||||
@@ -301,7 +315,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
const file = `${process.env.WINDIR!.replaceAll("\\", "/")}/win.ini`
|
const file = `${process.env.WINDIR!.replaceAll("\\", "/")}/win.ini`
|
||||||
await bash.execute(
|
await bash.execute(
|
||||||
@@ -331,7 +345,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await expect(
|
await expect(
|
||||||
@@ -359,7 +373,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await expect(
|
await expect(
|
||||||
@@ -388,7 +402,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await expect(
|
await expect(
|
||||||
@@ -416,7 +430,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await expect(
|
await expect(
|
||||||
@@ -448,7 +462,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
const root = path.parse(process.env.WINDIR!).root.replace(/[\\/]+$/, "")
|
const root = path.parse(process.env.WINDIR!).root.replace(/[\\/]+$/, "")
|
||||||
@@ -481,7 +495,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await bash.execute(
|
await bash.execute(
|
||||||
{
|
{
|
||||||
@@ -508,7 +522,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await expect(
|
await expect(
|
||||||
@@ -538,7 +552,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await expect(
|
await expect(
|
||||||
@@ -568,7 +582,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await bash.execute(
|
await bash.execute(
|
||||||
{
|
{
|
||||||
@@ -597,7 +611,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await bash.execute(
|
await bash.execute(
|
||||||
{
|
{
|
||||||
@@ -622,7 +636,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await expect(
|
await expect(
|
||||||
@@ -645,7 +659,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await expect(
|
await expect(
|
||||||
@@ -673,7 +687,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const want = Filesystem.normalizePathPattern(path.join(outerTmp.path, "*"))
|
const want = Filesystem.normalizePathPattern(path.join(outerTmp.path, "*"))
|
||||||
|
|
||||||
for (const dir of forms(outerTmp.path)) {
|
for (const dir of forms(outerTmp.path)) {
|
||||||
@@ -707,7 +721,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
const want = glob(path.join(os.tmpdir(), "*"))
|
const want = glob(path.join(os.tmpdir(), "*"))
|
||||||
@@ -737,7 +751,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
const want = glob(path.join(os.tmpdir(), "*"))
|
const want = glob(path.join(os.tmpdir(), "*"))
|
||||||
@@ -772,7 +786,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
const filepath = path.join(outerTmp.path, "outside.txt")
|
const filepath = path.join(outerTmp.path, "outside.txt")
|
||||||
@@ -803,7 +817,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await bash.execute(
|
await bash.execute(
|
||||||
{
|
{
|
||||||
@@ -823,7 +837,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await bash.execute(
|
await bash.execute(
|
||||||
{
|
{
|
||||||
@@ -844,7 +858,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await bash.execute(
|
await bash.execute(
|
||||||
{
|
{
|
||||||
@@ -864,7 +878,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const err = new Error("stop after permission")
|
const err = new Error("stop after permission")
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await expect(
|
await expect(
|
||||||
@@ -885,7 +899,7 @@ describe("tool.bash permissions", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
const requests: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||||
await bash.execute({ command: "ls -la", description: "List" }, capture(requests))
|
await bash.execute({ command: "ls -la", description: "List" }, capture(requests))
|
||||||
const bashReq = requests.find((r) => r.permission === "bash")
|
const bashReq = requests.find((r) => r.permission === "bash")
|
||||||
@@ -901,7 +915,7 @@ describe("tool.bash abort", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
const collected: string[] = []
|
const collected: string[] = []
|
||||||
const result = bash.execute(
|
const result = bash.execute(
|
||||||
@@ -933,7 +947,7 @@ describe("tool.bash abort", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const result = await bash.execute(
|
const result = await bash.execute(
|
||||||
{
|
{
|
||||||
command: `echo started && sleep 60`,
|
command: `echo started && sleep 60`,
|
||||||
@@ -952,7 +966,7 @@ describe("tool.bash abort", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const result = await bash.execute(
|
const result = await bash.execute(
|
||||||
{
|
{
|
||||||
command: `echo stdout_msg && echo stderr_msg >&2`,
|
command: `echo stdout_msg && echo stderr_msg >&2`,
|
||||||
@@ -971,7 +985,7 @@ describe("tool.bash abort", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const result = await bash.execute(
|
const result = await bash.execute(
|
||||||
{
|
{
|
||||||
command: `exit 42`,
|
command: `exit 42`,
|
||||||
@@ -988,7 +1002,7 @@ describe("tool.bash abort", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const updates: string[] = []
|
const updates: string[] = []
|
||||||
const result = await bash.execute(
|
const result = await bash.execute(
|
||||||
{
|
{
|
||||||
@@ -1016,7 +1030,7 @@ describe("tool.bash truncation", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const lineCount = Truncate.MAX_LINES + 500
|
const lineCount = Truncate.MAX_LINES + 500
|
||||||
const result = await bash.execute(
|
const result = await bash.execute(
|
||||||
{
|
{
|
||||||
@@ -1036,7 +1050,7 @@ describe("tool.bash truncation", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const byteCount = Truncate.MAX_BYTES + 10000
|
const byteCount = Truncate.MAX_BYTES + 10000
|
||||||
const result = await bash.execute(
|
const result = await bash.execute(
|
||||||
{
|
{
|
||||||
@@ -1056,7 +1070,7 @@ describe("tool.bash truncation", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const result = await bash.execute(
|
const result = await bash.execute(
|
||||||
{
|
{
|
||||||
command: "echo hello",
|
command: "echo hello",
|
||||||
@@ -1074,7 +1088,7 @@ describe("tool.bash truncation", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: projectRoot,
|
directory: projectRoot,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const bash = await BashTool.init()
|
const bash = await initBash()
|
||||||
const lineCount = Truncate.MAX_LINES + 100
|
const lineCount = Truncate.MAX_LINES + 100
|
||||||
const result = await bash.execute(
|
const result = await bash.execute(
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user