diff --git a/app/components/chat/Artifact.tsx b/app/components/chat/Artifact.tsx index f8cd72b..8306225 100644 --- a/app/components/chat/Artifact.tsx +++ b/app/components/chat/Artifact.tsx @@ -151,7 +151,13 @@ const ActionList = memo(({ actions }: ActionListProps) => {
{status === 'running' ? ( -
+ <> + {type !== 'start' ? ( +
+ ) : ( +
+ )} + ) : status === 'pending' ? (
) : status === 'complete' ? ( @@ -177,7 +183,7 @@ const ActionList = memo(({ actions }: ActionListProps) => {
) : null}
- {type === 'shell' && ( + {(type === 'shell' || type === 'start') && ( { const isActive = activeTerminal === index; if (index == 0) { - console.log('starting bolt terminal'); + logger.info('Starting bolt terminal'); return ( { toolCallId: string; @@ -40,6 +41,7 @@ function extractModelFromMessage(message: Message): { model: string; content: st export function streamText(messages: Messages, env: Env, options?: StreamingOptions) { let currentModel = DEFAULT_MODEL; + logger.debug('model List', JSON.stringify(MODEL_LIST, null, 2)) const processedMessages = messages.map((message) => { if (message.role === 'user') { const { model, content } = extractModelFromMessage(message); diff --git a/app/lib/runtime/action-runner.ts b/app/lib/runtime/action-runner.ts index 3f03bb1..e659110 100644 --- a/app/lib/runtime/action-runner.ts +++ b/app/lib/runtime/action-runner.ts @@ -116,7 +116,7 @@ export class ActionRunner { break; } case 'start': { - await this.#runStartAction(action); + await this.#runStartAction(action) break; } } @@ -124,6 +124,7 @@ export class ActionRunner { this.#updateAction(actionId, { status: action.abortSignal.aborted ? 'aborted' : 'complete' }); } catch (error) { this.#updateAction(actionId, { status: 'failed', error: 'Action failed' }); + logger.error(`[${action.type}]:Action failed\n\n`, error); // re-throw the error to be caught in the promise chain throw error; @@ -140,8 +141,9 @@ export class ActionRunner { unreachable('Shell terminal not found'); } const resp = await shell.executeCommand(this.runnerId.get(), action.content) + logger.debug(`${action.type} Shell Response: [exit code:${resp?.exitCode}]`) if (resp?.exitCode != 0) { - throw new Error("Failed To Start Application"); + throw new Error("Failed To Execute Shell Command"); } } @@ -159,10 +161,12 @@ export class ActionRunner { unreachable('Shell terminal not found'); } const resp = await shell.executeCommand(this.runnerId.get(), action.content) + logger.debug(`${action.type} Shell Response: [exit code:${resp?.exitCode}]`) + if (resp?.exitCode != 0) { throw new Error("Failed To Start Application"); - } + return resp } async #runFileAction(action: ActionState) { @@ -193,24 +197,6 @@ export class ActionRunner { logger.error('Failed to write file\n\n', error); } } - async getCurrentExecutionResult(output: ReadableStreamDefaultReader) { - let fullOutput = ''; - let exitCode: number = 0; - while (true) { - const { value, done } = await output.read(); - if (done) break; - const text = value || ''; - fullOutput += text; - // Check if command completion signal with exit code - const exitMatch = fullOutput.match(/\]654;exit=-?\d+:(\d+)/); - if (exitMatch) { - exitCode = parseInt(exitMatch[1], 10); - break; - } - } - return { output: fullOutput, exitCode }; - } - #updateAction(id: string, newState: ActionStateUpdate) { const actions = this.actions.get(); diff --git a/app/utils/shell.ts b/app/utils/shell.ts index 3d4e887..d45e8a6 100644 --- a/app/utils/shell.ts +++ b/app/utils/shell.ts @@ -60,8 +60,9 @@ export class BoltShell { #webcontainer: WebContainer | undefined #terminal: ITerminal | undefined #process: WebContainerProcess | undefined - executionState = atom<{ sessionId: string, active: boolean } | undefined>() - #outputStream: ReadableStream | undefined + executionState = atom<{ sessionId: string, active: boolean, executionPrms?: Promise } | undefined>() + #outputStream: ReadableStreamDefaultReader | undefined + #shellInputStream: WritableStreamDefaultWriter | undefined constructor() { this.#readyPromise = new Promise((resolve) => { this.#initialized = resolve @@ -78,7 +79,8 @@ export class BoltShell { } let { process, output } = await this.newBoltShellProcess(webcontainer, terminal) this.#process = process - this.#outputStream = output + this.#outputStream = output.getReader() + await this.waitTillOscCode('interactive') this.#initialized?.() } get terminal() { @@ -92,12 +94,21 @@ export class BoltShell { return } let state = this.executionState.get() - if (state && state.sessionId !== sessionId && state.active) { - this.terminal.input('\x03'); + + //interrupt the current execution + // this.#shellInputStream?.write('\x03'); + this.terminal.input('\x03'); + if (state && state.executionPrms) { + await state.executionPrms } - this.executionState.set({ sessionId, active: true }) + //start a new execution this.terminal.input(command.trim() + '\n'); - let resp = await this.getCurrentExecutionResult() + + //wait for the execution to finish + let executionPrms = this.getCurrentExecutionResult() + this.executionState.set({ sessionId, active: true, executionPrms }) + + let resp = await executionPrms this.executionState.set({ sessionId, active: false }) return resp @@ -114,6 +125,7 @@ export class BoltShell { }); const input = process.input.getWriter(); + this.#shellInputStream = input; const [internalOutput, terminalOutput] = process.output.tee(); const jshReady = withResolvers(); @@ -151,10 +163,14 @@ export class BoltShell { return { process, output: internalOutput }; } async getCurrentExecutionResult() { + let { output, exitCode } = await this.waitTillOscCode('exit') + return { output, exitCode }; + } + async waitTillOscCode(waitCode: string) { let fullOutput = ''; let exitCode: number = 0; - if (!this.#outputStream) return; - let tappedStream = this.#outputStream.getReader() + if (!this.#outputStream) return { output: fullOutput, exitCode }; + let tappedStream = this.#outputStream while (true) { const { value, done } = await tappedStream.read(); @@ -163,11 +179,11 @@ export class BoltShell { fullOutput += text; // Check if command completion signal with exit code - const exitMatch = fullOutput.match(/\]654;exit=-?\d+:(\d+)/); - if (exitMatch) { - console.log(exitMatch); - exitCode = parseInt(exitMatch[1], 10); - tappedStream.releaseLock() + const [, osc, , pid, code] = text.match(/\x1b\]654;([^\x07=]+)=?((-?\d+):(\d+))?\x07/) || []; + if (osc === 'exit') { + exitCode = parseInt(code, 10); + } + if (osc === waitCode) { break; } }