fix(tui): keep background shell spinner active
This commit is contained in:
parent
0a60662d71
commit
24ab17e718
7 changed files with 159 additions and 28 deletions
|
|
@ -39,6 +39,7 @@ export const Input = Schema.Struct({
|
|||
|
||||
const StructuredOutput = Schema.Struct({
|
||||
exit: Schema.Number.pipe(Schema.optional),
|
||||
shellID: Schema.String.pipe(Schema.optional),
|
||||
truncated: Schema.Boolean,
|
||||
timeout: Schema.Boolean.pipe(Schema.optional),
|
||||
})
|
||||
|
|
@ -142,6 +143,7 @@ export const Plugin = {
|
|||
toStructuredOutput: ({ output }) => ({
|
||||
truncated: output.truncated,
|
||||
...(output.exit === undefined ? {} : { exit: output.exit }),
|
||||
...(output.shellID === undefined ? {} : { shellID: output.shellID }),
|
||||
...(output.timeout === undefined ? {} : { timeout: output.timeout }),
|
||||
}),
|
||||
toModelOutput: ({ output }) => {
|
||||
|
|
@ -185,16 +187,16 @@ export const Plugin = {
|
|||
const timeout = input.timeout ?? DEFAULT_TIMEOUT_MS
|
||||
|
||||
if (input.background === true) {
|
||||
const background = yield* shell.create({
|
||||
command: input.command,
|
||||
cwd: target.canonical,
|
||||
timeout,
|
||||
metadata: { sessionID: context.sessionID },
|
||||
})
|
||||
const run = Effect.fn("ShellTool.run")(function* () {
|
||||
const info = yield* shell.create({
|
||||
command: input.command,
|
||||
cwd: target.canonical,
|
||||
timeout,
|
||||
metadata: { sessionID: context.sessionID },
|
||||
})
|
||||
return yield* Effect.gen(function* () {
|
||||
const final = yield* shell.wait(info.id)
|
||||
const page = yield* shell.output(info.id, { limit: MAX_CAPTURE_BYTES })
|
||||
const final = yield* shell.wait(background.id)
|
||||
const page = yield* shell.output(background.id, { limit: MAX_CAPTURE_BYTES })
|
||||
|
||||
if (final.status === "timeout")
|
||||
return `Command exceeded timeout of ${timeout} ms. Retry with a larger timeout if the command is expected to take longer.`
|
||||
|
|
@ -203,7 +205,7 @@ export const Plugin = {
|
|||
const body = page.output || "(no output)"
|
||||
const notice = truncated ? `\n\n[output truncated; full output saved to: ${final.file}]` : ""
|
||||
return `${body}${notice}`
|
||||
}).pipe(Effect.onInterrupt(() => shell.remove(info.id).pipe(Effect.ignore)))
|
||||
}).pipe(Effect.onInterrupt(() => shell.remove(background.id).pipe(Effect.ignore)))
|
||||
})
|
||||
|
||||
const info = yield* runtime.job.start({
|
||||
|
|
@ -217,6 +219,7 @@ export const Plugin = {
|
|||
yield* notifyWhenDone(context.sessionID, context.toolCallID, input.command)
|
||||
return {
|
||||
output: BACKGROUND_STARTED,
|
||||
shellID: background.id,
|
||||
truncated: false,
|
||||
status: "running" as const,
|
||||
...(warnings.length ? { warnings } : {}),
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import { SessionMessage } from "@opencode-ai/core/session/message"
|
|||
import { SessionStore } from "@opencode-ai/core/session/store"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
import { PluginRuntime } from "@opencode-ai/core/plugin/runtime"
|
||||
import { Shell } from "@opencode-ai/core/shell"
|
||||
import { Shell as ShellSchema } from "@opencode-ai/schema/shell"
|
||||
import { ShellTool } from "@opencode-ai/core/tool/shell"
|
||||
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||
|
|
@ -398,6 +400,31 @@ describe("ShellTool", () => {
|
|||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]().then(() => undefined)),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("returns the shell id for a background command", () =>
|
||||
Effect.acquireUseRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
(tmp) => {
|
||||
reset()
|
||||
return withSession(tmp.path, (registry) =>
|
||||
Effect.gen(function* () {
|
||||
const settled = yield* settleTool(registry, call({ command: idleCommand, background: true }))
|
||||
const structured = settled.output?.structured as Record<string, unknown> | undefined
|
||||
const shellID = typeof structured?.shellID === "string" ? structured.shellID : undefined
|
||||
expect(settled.output?.structured).toMatchObject({ truncated: false })
|
||||
expect(shellID).toStartWith("sh_")
|
||||
|
||||
const shell = yield* Shell.Service
|
||||
if (!shellID) return
|
||||
const id = ShellSchema.ID.make(shellID)
|
||||
expect((yield* shell.list()).map((info) => info.id)).toContain(id)
|
||||
yield* shell.remove(id)
|
||||
}),
|
||||
)
|
||||
},
|
||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]().then(() => undefined)),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
test("keeps locked deferred parity TODOs visible", async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue