{"version":3,"file":"bash-execution-controller.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/bash-execution-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAIhE,kEAAkE;AAClE,MAAM,WAAW,2BAA2B;IAC3C,0EAA0E;IAC1E,IAAI,OAAO,IAAI,YAAY,CAAC;IAC5B,EAAE,EAAE,GAAG,CAAC;IACR,4DAA4D;IAC5D,wBAAwB,EAAE,SAAS,CAAC;IACpC,2BAA2B;IAC3B,aAAa,EAAE,SAAS,CAAC;IACzB,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,qBAAa,uBAAuB;IAOvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,OAAO,CAAC,aAAa,CAAiD;IAGtE,OAAO,CAAC,qBAAqB,CAAgC;IAE7D,YAA6B,IAAI,EAAE,2BAA2B,EAAI;IAElE,OAAO,KAAK,OAAO,GAElB;IAED,6DAA6D;IAC7D,0BAA0B,IAAI,IAAI,CAMjC;IAEK,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,kBAAkB,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAqFlF;CACD","sourcesContent":["/**\n * Bash command execution for the interactive mode (the `!cmd` prompt mode).\n *\n * Runs a bash command through the session (letting extensions intercept via\n * the user_bash event), renders a BashExecutionComponent for it, and streams\n * output into that component. Commands started while the agent is streaming are\n * parked in the pending area and moved into the chat transcript once the turn\n * finishes. Extracted from interactive-mode.ts behind a narrow\n * BashExecutionControllerDeps interface.\n */\n\nimport type { Container, TUI } from \"@kolisachint/hoocode-tui\";\nimport type { AgentSession } from \"../../core/agent-session.js\";\nimport type { TruncationResult } from \"../../core/tools/truncate.js\";\nimport { BashExecutionComponent } from \"./components/bash-execution.js\";\n\n/** The slice of the interactive mode the bash execution needs. */\nexport interface BashExecutionControllerDeps {\n\t/** The active session (read at call time; the session can be swapped). */\n\tget session(): AgentSession;\n\tui: TUI;\n\t/** Holds bash rows started while the agent is streaming. */\n\tpendingMessagesContainer: Container;\n\t/** The chat transcript. */\n\tchatContainer: Container;\n\tshowError(errorMessage: string): void;\n}\n\nexport class BashExecutionController {\n\t// Current bash execution component\n\tprivate bashComponent: BashExecutionComponent | undefined = undefined;\n\n\t// Pending bash components (shown in pending area, moved to chat on submit)\n\tprivate pendingBashComponents: BashExecutionComponent[] = [];\n\n\tconstructor(private readonly deps: BashExecutionControllerDeps) {}\n\n\tprivate get session(): AgentSession {\n\t\treturn this.deps.session;\n\t}\n\n\t/** Move pending bash components from pending area to chat */\n\tflushPendingBashComponents(): void {\n\t\tfor (const component of this.pendingBashComponents) {\n\t\t\tthis.deps.pendingMessagesContainer.removeChild(component);\n\t\t\tthis.deps.chatContainer.addChild(component);\n\t\t}\n\t\tthis.pendingBashComponents = [];\n\t}\n\n\tasync handleBashCommand(command: string, excludeFromContext = false): Promise<void> {\n\t\tconst extensionRunner = this.session.extensionRunner;\n\n\t\t// Emit user_bash event to let extensions intercept\n\t\tconst eventResult = await extensionRunner.emitUserBash({\n\t\t\ttype: \"user_bash\",\n\t\t\tcommand,\n\t\t\texcludeFromContext,\n\t\t\tcwd: this.session.sessionManager.getCwd(),\n\t\t});\n\n\t\t// If extension returned a full result, use it directly\n\t\tif (eventResult?.result) {\n\t\t\tconst result = eventResult.result;\n\n\t\t\t// Create UI component for display\n\t\t\tthis.bashComponent = new BashExecutionComponent(command, this.deps.ui, excludeFromContext);\n\t\t\tif (this.session.isStreaming) {\n\t\t\t\tthis.deps.pendingMessagesContainer.addChild(this.bashComponent);\n\t\t\t\tthis.pendingBashComponents.push(this.bashComponent);\n\t\t\t} else {\n\t\t\t\tthis.deps.chatContainer.addChild(this.bashComponent);\n\t\t\t}\n\n\t\t\t// Show output and complete\n\t\t\tif (result.output) {\n\t\t\t\tthis.bashComponent.appendOutput(result.output);\n\t\t\t}\n\t\t\tthis.bashComponent.setComplete(\n\t\t\t\tresult.exitCode,\n\t\t\t\tresult.cancelled,\n\t\t\t\tresult.truncated ? ({ truncated: true, content: result.output } as TruncationResult) : undefined,\n\t\t\t\tresult.fullOutputPath,\n\t\t\t);\n\n\t\t\t// Record the result in session\n\t\t\tthis.session.recordBashResult(command, result, { excludeFromContext });\n\t\t\tthis.bashComponent = undefined;\n\t\t\tthis.deps.ui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\t// Normal execution path (possibly with custom operations)\n\t\tconst isDeferred = this.session.isStreaming;\n\t\tthis.bashComponent = new BashExecutionComponent(command, this.deps.ui, excludeFromContext);\n\n\t\tif (isDeferred) {\n\t\t\t// Show in pending area when agent is streaming\n\t\t\tthis.deps.pendingMessagesContainer.addChild(this.bashComponent);\n\t\t\tthis.pendingBashComponents.push(this.bashComponent);\n\t\t} else {\n\t\t\t// Show in chat immediately when agent is idle\n\t\t\tthis.deps.chatContainer.addChild(this.bashComponent);\n\t\t}\n\t\tthis.deps.ui.requestRender();\n\n\t\ttry {\n\t\t\tconst result = await this.session.executeBash(\n\t\t\t\tcommand,\n\t\t\t\t(chunk) => {\n\t\t\t\t\tif (this.bashComponent) {\n\t\t\t\t\t\tthis.bashComponent.appendOutput(chunk);\n\t\t\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ excludeFromContext, operations: eventResult?.operations },\n\t\t\t);\n\n\t\t\tif (this.bashComponent) {\n\t\t\t\tthis.bashComponent.setComplete(\n\t\t\t\t\tresult.exitCode,\n\t\t\t\t\tresult.cancelled,\n\t\t\t\t\tresult.truncated ? ({ truncated: true, content: result.output } as TruncationResult) : undefined,\n\t\t\t\t\tresult.fullOutputPath,\n\t\t\t\t);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tif (this.bashComponent) {\n\t\t\t\tthis.bashComponent.setComplete(undefined, false);\n\t\t\t}\n\t\t\tthis.deps.showError(`Bash command failed: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n\t\t}\n\n\t\tthis.bashComponent = undefined;\n\t\tthis.deps.ui.requestRender();\n\t}\n}\n"]}