Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions packages/opencode/src/effect/cross-spawn-spawner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,19 +268,34 @@ export const make = Effect.gen(function* () {
const proc = launch(command.command, command.args, opts)
let end = false
let exit: readonly [code: number | null, signal: NodeJS.Signals | null] | undefined
let spawned = false
proc.on("error", (err) => {
resume(Effect.fail(toPlatformError("spawn", err, command)))
if (!spawned) {
spawned = true
resume(Effect.fail(toPlatformError("spawn", err, command)))
}
})
proc.on("exit", (...args) => {
if (!spawned) {
spawned = true
resume(Effect.succeed([proc, signal]))
}
exit = args
})
proc.on("close", (...args) => {
if (!spawned) {
spawned = true
resume(Effect.succeed([proc, signal]))
}
if (end) return
end = true
Deferred.doneUnsafe(signal, Exit.succeed(exit ?? args))
})
proc.on("spawn", () => {
resume(Effect.succeed([proc, signal]))
if (!spawned) {
spawned = true
resume(Effect.succeed([proc, signal]))
}
})
return Effect.sync(() => {
proc.kill("SIGTERM")
Expand Down
36 changes: 25 additions & 11 deletions packages/opencode/src/tool/bash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Shell } from "@/shell/shell"
import { BashArity } from "@/permission/arity"
import { Truncate } from "./truncate"
import { Plugin } from "@/plugin"
import { Effect, Stream } from "effect"
import { Effect, Fiber, Stream } from "effect"
import { ChildProcess } from "effect/unstable/process"
import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"

Expand Down Expand Up @@ -384,6 +384,7 @@ export const BashTool = Tool.define(
let output = ""
let expired = false
let aborted = false
let last = 0

yield* ctx.metadata({
metadata: {
Expand All @@ -396,16 +397,20 @@ export const BashTool = Tool.define(
Effect.gen(function* () {
const handle = yield* spawner.spawn(cmd(input.shell, input.name, input.command, input.cwd, input.env))

yield* Effect.forkScoped(
Stream.runForEach(Stream.decodeText(handle.all), (chunk) => {
output += chunk
return ctx.metadata({
metadata: {
output: preview(output),
description: input.description,
},
})
}),
const streamFiber = yield* Effect.forkScoped(
Stream.runForEach(Stream.decodeText(handle.all), (chunk) =>
Effect.gen(function* () {
output += chunk
if (Date.now() - last <= 250) return
last = Date.now()
yield* ctx.metadata({
metadata: {
output: preview(output),
description: input.description,
},
})
}),
),
)

const abort = Effect.callback<void>((resume) => {
Expand All @@ -431,6 +436,15 @@ export const BashTool = Tool.define(
expired = true
yield* handle.kill({ forceKillAfter: "3 seconds" }).pipe(Effect.orDie)
}
if (exit.kind === "exit") {
yield* Fiber.join(streamFiber).pipe(Effect.ignore)
yield* ctx.metadata({
metadata: {
output: preview(output),
description: input.description,
},
})
}

return exit.kind === "exit" ? exit.code : null
}),
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/test/tool/bash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1077,7 +1077,7 @@ describe("tool.bash abort", () => {
const result = await Effect.runPromise(
bash.execute(
{
command: `echo first && sleep 0.1 && echo second`,
command: `echo first && sleep 0.3 && echo second`,
description: "Streaming test",
},
{
Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/test/tool/write.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ describe("tool.write", () => {

if (process.platform !== "win32") {
const stats = yield* Effect.promise(() => fs.stat(filepath))
expect(stats.mode & 0o777).toBe(0o644)
const mode = stats.mode & 0o777
expect(mode === 0o644 || mode === 0o664).toBe(true)
}
}),
),
Expand Down
Loading