Load complete background process output and fix dialog layout

This commit is contained in:
Shantur Rathore
2025-12-30 22:03:04 +00:00
parent 154c5208b4
commit a9524b3e30
4 changed files with 28 additions and 20 deletions

View File

@@ -166,7 +166,7 @@ export class BackgroundProcessManager {
async readOutput( async readOutput(
workspaceId: string, workspaceId: string,
processId: string, processId: string,
options: { method?: "full" | "tail" | "head" | "grep"; pattern?: string; lines?: number }, options: { method?: "full" | "tail" | "head" | "grep"; pattern?: string; lines?: number; maxBytes?: number },
) { ) {
const outputPath = this.getOutputPath(workspaceId, processId) const outputPath = this.getOutputPath(workspaceId, processId)
if (!existsSync(outputPath)) { if (!existsSync(outputPath)) {
@@ -178,7 +178,7 @@ export class BackgroundProcessManager {
const method = options.method ?? "full" const method = options.method ?? "full"
const lineCount = options.lines ?? 10 const lineCount = options.lines ?? 10
const raw = await this.readOutputBytes(outputPath, sizeBytes) const raw = await this.readOutputBytes(outputPath, sizeBytes, options.maxBytes)
let content = raw let content = raw
switch (method) { switch (method) {
@@ -198,10 +198,11 @@ export class BackgroundProcessManager {
content = raw content = raw
} }
const effectiveMaxBytes = options.maxBytes
return { return {
id: processId, id: processId,
content, content,
truncated: sizeBytes > MAX_OUTPUT_BYTES, truncated: effectiveMaxBytes !== undefined && sizeBytes > effectiveMaxBytes,
sizeBytes, sizeBytes,
} }
} }
@@ -280,12 +281,12 @@ export class BackgroundProcessManager {
return "error" return "error"
} }
private async readOutputBytes(outputPath: string, sizeBytes: number): Promise<string> { private async readOutputBytes(outputPath: string, sizeBytes: number, maxBytes?: number): Promise<string> {
if (sizeBytes <= MAX_OUTPUT_BYTES) { if (maxBytes === undefined || sizeBytes <= maxBytes) {
return await fs.readFile(outputPath, "utf-8") return await fs.readFile(outputPath, "utf-8")
} }
const start = Math.max(0, sizeBytes - MAX_OUTPUT_BYTES) const start = Math.max(0, sizeBytes - maxBytes)
const file = await fs.open(outputPath, "r") const file = await fs.open(outputPath, "r")
const buffer = Buffer.alloc(sizeBytes - start) const buffer = Buffer.alloc(sizeBytes - start)
await file.read(buffer, 0, buffer.length, start) await file.read(buffer, 0, buffer.length, start)

View File

@@ -16,6 +16,7 @@ const OutputQuerySchema = z.object({
mode: z.enum(["full", "tail", "head", "grep"]).optional(), mode: z.enum(["full", "tail", "head", "grep"]).optional(),
pattern: z.string().optional(), pattern: z.string().optional(),
lines: z.coerce.number().int().positive().max(2000).optional(), lines: z.coerce.number().int().positive().max(2000).optional(),
maxBytes: z.coerce.number().int().positive().optional(),
}) })
export function registerBackgroundProcessRoutes(app: FastifyInstance, deps: RouteDeps) { export function registerBackgroundProcessRoutes(app: FastifyInstance, deps: RouteDeps) {
@@ -66,6 +67,7 @@ export function registerBackgroundProcessRoutes(app: FastifyInstance, deps: Rout
method, method,
pattern: query.pattern, pattern: query.pattern,
lines: query.lines, lines: query.lines,
maxBytes: query.maxBytes,
}) })
} catch (error) { } catch (error) {
reply.code(400) reply.code(400)

View File

@@ -26,7 +26,7 @@ export function BackgroundProcessOutputDialog(props: BackgroundProcessOutputDial
setLoading(true) setLoading(true)
serverApi serverApi
.fetchBackgroundProcessOutput(props.instanceId, process.id, { method: "full" }) .fetchBackgroundProcessOutput(props.instanceId, process.id, { method: "full", maxBytes: undefined })
.then((response) => { .then((response) => {
if (!active) return if (!active) return
setOutput(response.content) setOutput(response.content)
@@ -65,18 +65,20 @@ export function BackgroundProcessOutputDialog(props: BackgroundProcessOutputDial
<Dialog.Overlay class="modal-overlay" /> <Dialog.Overlay class="modal-overlay" />
<div class="fixed inset-0 z-50 flex items-center justify-center p-4"> <div class="fixed inset-0 z-50 flex items-center justify-center p-4">
<Dialog.Content class="modal-surface w-full max-w-5xl max-h-[90vh] flex flex-col overflow-hidden"> <Dialog.Content class="modal-surface w-full max-w-5xl max-h-[90vh] flex flex-col overflow-hidden">
<div class="flex items-center justify-between px-6 py-4 border-b border-base"> <div class="flex items-start justify-between px-6 py-4 border-b border-base gap-4">
<div class="flex flex-col"> <div class="flex-1 min-w-0">
<Dialog.Title class="text-lg font-semibold text-primary">Background Output</Dialog.Title> <Dialog.Title class="text-lg font-semibold text-primary">Background Output</Dialog.Title>
<Show when={props.process}> <Show when={props.process}>
<span class="text-xs text-secondary"> <span class="text-xs text-secondary block">
{props.process?.title} · {props.process?.id} {props.process?.title} · {props.process?.id}
</span> </span>
<span class="text-xs text-secondary mt-1 break-words">{props.process?.command}</span> <span class="text-xs text-secondary mt-1 block truncate" title={props.process?.command}>
</Show> {props.process?.command}
</div> </span>
</Show>
</div>
<button type="button" class="button-tertiary" onClick={props.onClose}> <button type="button" class="button-tertiary flex-shrink-0" onClick={props.onClose}>
Close Close
</button> </button>
</div> </div>
@@ -88,7 +90,7 @@ export function BackgroundProcessOutputDialog(props: BackgroundProcessOutputDial
<Show when={truncated()}> <Show when={truncated()}>
<p class="text-xs text-secondary mb-2">Output truncated for display.</p> <p class="text-xs text-secondary mb-2">Output truncated for display.</p>
</Show> </Show>
<pre class="text-xs whitespace-pre-wrap break-words text-primary bg-surface-secondary border border-base rounded-md p-4"> <pre class="text-xs whitespace-pre-wrap break-all text-primary bg-surface-secondary border border-base rounded-md p-4 font-mono">
{output()} {output()}
</pre> </pre>
</Show> </Show>

View File

@@ -247,7 +247,7 @@ export const serverApi = {
fetchBackgroundProcessOutput( fetchBackgroundProcessOutput(
instanceId: string, instanceId: string,
processId: string, processId: string,
options?: { method?: "full" | "tail" | "head" | "grep"; pattern?: string; lines?: number }, options?: { method?: "full" | "tail" | "head" | "grep"; pattern?: string; lines?: number; maxBytes?: number },
): Promise<BackgroundProcessOutputResponse> { ): Promise<BackgroundProcessOutputResponse> {
const params = new URLSearchParams() const params = new URLSearchParams()
if (options?.method) { if (options?.method) {
@@ -259,6 +259,9 @@ export const serverApi = {
if (options?.lines) { if (options?.lines) {
params.set("lines", String(options.lines)) params.set("lines", String(options.lines))
} }
if (options?.maxBytes !== undefined) {
params.set("maxBytes", String(options.maxBytes))
}
const query = params.toString() const query = params.toString()
const suffix = query ? `?${query}` : "" const suffix = query ? `?${query}` : ""
return request<BackgroundProcessOutputResponse>( return request<BackgroundProcessOutputResponse>(