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
47 changes: 37 additions & 10 deletions packages/opencode/src/tool/bash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Shell } from "@/shell/shell"
import { BashArity } from "@/permission/arity"
import { Truncate } from "./truncate"
import { Plugin } from "@/plugin"
import { Cause, Effect, Exit, Stream } from "effect"
import { Cause, Effect, Exit, Stream, Fiber } from "effect"
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"

Expand Down Expand Up @@ -341,16 +341,42 @@ async function run(
Effect.gen(function* () {
const handle = yield* spawner.spawn(cmd(input.shell, input.name, input.command, input.cwd, input.env))

yield* Effect.forkScoped(
let lastUpdate = 0
let timeoutId: any = null

const flush = () => {
if (timeoutId) {
clearTimeout(timeoutId)
timeoutId = null
}
lastUpdate = Date.now()
ctx.metadata({
metadata: {
output: preview(output),
description: input.description,
},
})
}

yield* Effect.addFinalizer(() =>
Effect.sync(() => {
if (timeoutId) {
clearTimeout(timeoutId)
timeoutId = null
}
}),
)

const streamFiber = yield* Effect.forkScoped(
Stream.runForEach(Stream.decodeText(handle.all), (chunk) =>
Effect.sync(() => {
output += chunk
ctx.metadata({
metadata: {
output: preview(output),
description: input.description,
},
})
const now = Date.now()
if (now - lastUpdate > 250) {
flush()
} else if (!timeoutId) {
timeoutId = setTimeout(flush, 250)
}
}),
),
)
Expand All @@ -373,10 +399,11 @@ async function run(
if (exit.kind === "abort") {
aborted = true
yield* handle.kill({ forceKillAfter: "3 seconds" }).pipe(Effect.orDie)
}
if (exit.kind === "timeout") {
} else if (exit.kind === "timeout") {
expired = true
yield* handle.kill({ forceKillAfter: "3 seconds" }).pipe(Effect.orDie)
} else {
yield* Fiber.join(streamFiber).pipe(Effect.ignore)
}

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 @@ -992,7 +992,7 @@ describe("tool.bash abort", () => {
const updates: string[] = []
const result = await 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 @@ -171,7 +171,8 @@ describe("tool.write", () => {
// On Unix systems, check permissions
if (process.platform !== "win32") {
const stats = await fs.stat(filepath)
expect(stats.mode & 0o777).toBe(0o644)
const mode = stats.mode & 0o777
expect(mode === 0o644 || mode === 0o664).toBe(true)
}
},
})
Expand Down
Loading