mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
fix: handle daemon processes without blocking UI
- Add daemon process detection utility with common service patterns - Modify executeCommandTool to detect and handle daemons as background processes - Add configuration options for custom daemon patterns - Add comprehensive tests for daemon detection logic Fixes #8636
This commit is contained in:
parent
6b8c21f873
commit
e06469a31c
5 changed files with 523 additions and 33 deletions
|
|
@ -17,6 +17,13 @@ import { TerminalRegistry } from "../../integrations/terminal/TerminalRegistry"
|
|||
import { Terminal } from "../../integrations/terminal/Terminal"
|
||||
import { Package } from "../../shared/package"
|
||||
import { t } from "../../i18n"
|
||||
import {
|
||||
isDaemonCommand,
|
||||
getDaemonMessage,
|
||||
getServiceType,
|
||||
addDaemonPatterns,
|
||||
clearUserDaemonPatterns,
|
||||
} from "../../utils/daemon-detector"
|
||||
|
||||
class ShellIntegrationError extends Error {}
|
||||
|
||||
|
|
@ -173,6 +180,24 @@ export async function executeCommand(
|
|||
return [false, `Working directory '${workingDir}' does not exist.`]
|
||||
}
|
||||
|
||||
// Check configuration for daemon detection
|
||||
const disableDaemonDetection = vscode.workspace
|
||||
.getConfiguration(Package.name)
|
||||
.get<boolean>("disableDaemonDetection", false)
|
||||
|
||||
// Load user-defined daemon patterns from configuration
|
||||
if (!disableDaemonDetection) {
|
||||
clearUserDaemonPatterns()
|
||||
const userDaemonPatterns = vscode.workspace.getConfiguration(Package.name).get<string[]>("daemonCommands", [])
|
||||
if (userDaemonPatterns.length > 0) {
|
||||
addDaemonPatterns(userDaemonPatterns)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this is a daemon/long-running process
|
||||
const isDaemon = !disableDaemonDetection && isDaemonCommand(command)
|
||||
const serviceType = isDaemon ? getServiceType(command) : null
|
||||
|
||||
let message: { text?: string; images?: string[] } | undefined
|
||||
let runInBackground = false
|
||||
let completed = false
|
||||
|
|
@ -252,47 +277,104 @@ export async function executeCommand(
|
|||
const process = terminal.runCommand(command, callbacks)
|
||||
task.terminalProcess = process
|
||||
|
||||
// Implement command execution timeout (skip if timeout is 0).
|
||||
if (commandExecutionTimeout > 0) {
|
||||
let timeoutId: NodeJS.Timeout | undefined
|
||||
let isTimedOut = false
|
||||
// If this is a daemon process, handle it differently
|
||||
if (isDaemon) {
|
||||
// Wait for a short time to capture initial output and check for startup errors
|
||||
const initialOutputTimeout = 3000 // 3 seconds to capture initial output
|
||||
let initialOutputCaptured = false
|
||||
|
||||
const timeoutPromise = new Promise<void>((_, reject) => {
|
||||
timeoutId = setTimeout(() => {
|
||||
isTimedOut = true
|
||||
task.terminalProcess?.abort()
|
||||
reject(new Error(`Command execution timed out after ${commandExecutionTimeout}ms`))
|
||||
}, commandExecutionTimeout)
|
||||
const initialOutputPromise = new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
initialOutputCaptured = true
|
||||
resolve()
|
||||
}, initialOutputTimeout)
|
||||
})
|
||||
|
||||
try {
|
||||
await Promise.race([process, timeoutPromise])
|
||||
} catch (error) {
|
||||
if (isTimedOut) {
|
||||
const status: CommandExecutionStatus = { executionId, status: "timeout" }
|
||||
provider?.postMessageToWebview({ type: "commandExecutionStatus", text: JSON.stringify(status) })
|
||||
await task.say("error", t("common:errors:command_timeout", { seconds: commandExecutionTimeoutSeconds }))
|
||||
task.terminalProcess = undefined
|
||||
|
||||
return [
|
||||
false,
|
||||
`The command was terminated after exceeding a user-configured ${commandExecutionTimeoutSeconds}s timeout. Do not try to re-run the command.`,
|
||||
]
|
||||
}
|
||||
throw error
|
||||
} finally {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId)
|
||||
// Also resolve if the process completes early (e.g., startup error)
|
||||
const processCompletePromise = new Promise<void>((resolve) => {
|
||||
const checkComplete = () => {
|
||||
if (completed) {
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
// Check periodically if process completed
|
||||
const interval = setInterval(() => {
|
||||
checkComplete()
|
||||
if (completed || initialOutputCaptured) {
|
||||
clearInterval(interval)
|
||||
}
|
||||
}, 100)
|
||||
})
|
||||
|
||||
// Wait for either initial output timeout or process completion
|
||||
await Promise.race([initialOutputPromise, processCompletePromise])
|
||||
|
||||
// If the process completed quickly, it likely failed to start
|
||||
if (completed) {
|
||||
// Handle as normal - the process failed
|
||||
task.terminalProcess = undefined
|
||||
} else {
|
||||
// Process is still running - it's a daemon
|
||||
task.terminalProcess = undefined // Clear reference but don't abort
|
||||
|
||||
const daemonMessage = getDaemonMessage(command)
|
||||
await task.say("text", daemonMessage)
|
||||
|
||||
return [
|
||||
false,
|
||||
`Started ${serviceType} in the background from '${terminal.getCurrentWorkingDirectory().toPosix()}'.\n` +
|
||||
`The service is running and you can proceed with other tasks.\n` +
|
||||
`Current output:\n${result}\n` +
|
||||
`The terminal will continue to show output from this service.`,
|
||||
]
|
||||
}
|
||||
} else {
|
||||
// No timeout - just wait for the process to complete.
|
||||
try {
|
||||
await process
|
||||
} finally {
|
||||
task.terminalProcess = undefined
|
||||
// Normal command execution with timeout handling
|
||||
// Implement command execution timeout (skip if timeout is 0).
|
||||
if (commandExecutionTimeout > 0) {
|
||||
let timeoutId: NodeJS.Timeout | undefined
|
||||
let isTimedOut = false
|
||||
|
||||
const timeoutPromise = new Promise<void>((_, reject) => {
|
||||
timeoutId = setTimeout(() => {
|
||||
isTimedOut = true
|
||||
task.terminalProcess?.abort()
|
||||
reject(new Error(`Command execution timed out after ${commandExecutionTimeout}ms`))
|
||||
}, commandExecutionTimeout)
|
||||
})
|
||||
|
||||
try {
|
||||
await Promise.race([process, timeoutPromise])
|
||||
} catch (error) {
|
||||
if (isTimedOut) {
|
||||
const status: CommandExecutionStatus = { executionId, status: "timeout" }
|
||||
provider?.postMessageToWebview({ type: "commandExecutionStatus", text: JSON.stringify(status) })
|
||||
await task.say(
|
||||
"error",
|
||||
t("common:errors:command_timeout", { seconds: commandExecutionTimeoutSeconds }),
|
||||
)
|
||||
task.terminalProcess = undefined
|
||||
|
||||
return [
|
||||
false,
|
||||
`The command was terminated after exceeding a user-configured ${commandExecutionTimeoutSeconds}s timeout. Do not try to re-run the command.`,
|
||||
]
|
||||
}
|
||||
throw error
|
||||
} finally {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
|
||||
task.terminalProcess = undefined
|
||||
}
|
||||
} else {
|
||||
// No timeout - just wait for the process to complete.
|
||||
try {
|
||||
await process
|
||||
} finally {
|
||||
task.terminalProcess = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -377,6 +377,19 @@
|
|||
"default": false,
|
||||
"description": "%commands.preventCompletionWithOpenTodos.description%"
|
||||
},
|
||||
"roo-cline.daemonCommands": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"description": "%commands.daemonCommands.description%"
|
||||
},
|
||||
"roo-cline.disableDaemonDetection": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%commands.disableDaemonDetection.description%"
|
||||
},
|
||||
"roo-cline.vsCodeLmModelSelector": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@
|
|||
"commands.commandExecutionTimeout.description": "Maximum time in seconds to wait for command execution to complete before timing out (0 = no timeout, 1-600s, default: 0s)",
|
||||
"commands.commandTimeoutAllowlist.description": "Command prefixes that are excluded from the command execution timeout. Commands matching these prefixes will run without timeout restrictions.",
|
||||
"commands.preventCompletionWithOpenTodos.description": "Prevent task completion when there are incomplete todos in the todo list",
|
||||
"commands.daemonCommands.description": "Additional command patterns to identify as daemon/long-running processes. These commands will be handled as background services that continue running without blocking the workflow.",
|
||||
"commands.disableDaemonDetection.description": "Disable automatic detection of daemon/long-running processes. When disabled, all commands will wait for completion before proceeding.",
|
||||
"settings.vsCodeLmModelSelector.description": "Settings for VSCode Language Model API",
|
||||
"settings.vsCodeLmModelSelector.vendor.description": "The vendor of the language model (e.g. copilot)",
|
||||
"settings.vsCodeLmModelSelector.family.description": "The family of the language model (e.g. gpt-4)",
|
||||
|
|
|
|||
227
src/utils/__tests__/daemon-detector.spec.ts
Normal file
227
src/utils/__tests__/daemon-detector.spec.ts
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
import { describe, it, expect, beforeEach } from "vitest"
|
||||
import {
|
||||
isDaemonCommand,
|
||||
addDaemonPatterns,
|
||||
clearUserDaemonPatterns,
|
||||
getDaemonMessage,
|
||||
getServiceType,
|
||||
} from "../daemon-detector"
|
||||
|
||||
describe("daemon-detector", () => {
|
||||
beforeEach(() => {
|
||||
// Clear any user patterns before each test
|
||||
clearUserDaemonPatterns()
|
||||
})
|
||||
|
||||
describe("isDaemonCommand", () => {
|
||||
describe("Java/Spring Boot patterns", () => {
|
||||
it("should detect mvn spring-boot:run", () => {
|
||||
expect(isDaemonCommand("mvn spring-boot:run")).toBe(true)
|
||||
expect(isDaemonCommand("MVN SPRING-BOOT:RUN")).toBe(true)
|
||||
expect(isDaemonCommand("mvn spring-boot:run --debug")).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect gradle bootRun", () => {
|
||||
expect(isDaemonCommand("gradle bootRun")).toBe(true)
|
||||
expect(isDaemonCommand('gradle bootRun --args="--spring.profiles.active=dev"')).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect java -jar commands", () => {
|
||||
expect(isDaemonCommand("java -jar myapp.jar")).toBe(true)
|
||||
expect(isDaemonCommand("java -jar /path/to/app.jar")).toBe(true)
|
||||
expect(isDaemonCommand("java -Xmx512m -jar application.jar")).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Node.js patterns", () => {
|
||||
it("should detect npm start/dev/serve commands", () => {
|
||||
expect(isDaemonCommand("npm start")).toBe(true)
|
||||
expect(isDaemonCommand("npm run start")).toBe(true)
|
||||
expect(isDaemonCommand("npm run dev")).toBe(true)
|
||||
expect(isDaemonCommand("npm serve")).toBe(true)
|
||||
expect(isDaemonCommand("npm run watch")).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect yarn commands", () => {
|
||||
expect(isDaemonCommand("yarn start")).toBe(true)
|
||||
expect(isDaemonCommand("yarn dev")).toBe(true)
|
||||
expect(isDaemonCommand("yarn serve")).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect nodemon", () => {
|
||||
expect(isDaemonCommand("nodemon server.js")).toBe(true)
|
||||
expect(isDaemonCommand("nodemon --watch src app.js")).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect pm2", () => {
|
||||
expect(isDaemonCommand("pm2 start app.js")).toBe(true)
|
||||
expect(isDaemonCommand("pm2 start ecosystem.config.js")).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Python patterns", () => {
|
||||
it("should detect Python HTTP server", () => {
|
||||
expect(isDaemonCommand("python -m http.server")).toBe(true)
|
||||
expect(isDaemonCommand("python3 -m http.server 8000")).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect Django runserver", () => {
|
||||
expect(isDaemonCommand("python manage.py runserver")).toBe(true)
|
||||
expect(isDaemonCommand("python manage.py runserver 0.0.0.0:8000")).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect Flask run", () => {
|
||||
expect(isDaemonCommand("flask run")).toBe(true)
|
||||
expect(isDaemonCommand("flask run --host=0.0.0.0")).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect Python app.py", () => {
|
||||
expect(isDaemonCommand("python app.py")).toBe(true)
|
||||
expect(isDaemonCommand("python3 /path/to/app.py")).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Ruby patterns", () => {
|
||||
it("should detect Rails server", () => {
|
||||
expect(isDaemonCommand("rails server")).toBe(true)
|
||||
expect(isDaemonCommand("rails s")).toBe(true)
|
||||
expect(isDaemonCommand("rails server -p 3001")).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Docker patterns", () => {
|
||||
it("should detect docker run without --rm", () => {
|
||||
expect(isDaemonCommand("docker run nginx")).toBe(true)
|
||||
expect(isDaemonCommand("docker run -p 8080:80 nginx")).toBe(true)
|
||||
})
|
||||
|
||||
it("should not detect docker run with --rm", () => {
|
||||
expect(isDaemonCommand("docker run --rm nginx")).toBe(false)
|
||||
expect(isDaemonCommand("docker run --rm -it ubuntu bash")).toBe(false)
|
||||
})
|
||||
|
||||
it("should detect docker-compose up without -d", () => {
|
||||
expect(isDaemonCommand("docker-compose up")).toBe(true)
|
||||
expect(isDaemonCommand("docker-compose up web")).toBe(true)
|
||||
})
|
||||
|
||||
it("should not detect docker-compose up with -d", () => {
|
||||
expect(isDaemonCommand("docker-compose up -d")).toBe(false)
|
||||
expect(isDaemonCommand("docker-compose up -d web")).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Non-daemon commands", () => {
|
||||
it("should not detect regular commands", () => {
|
||||
expect(isDaemonCommand("ls -la")).toBe(false)
|
||||
expect(isDaemonCommand("git status")).toBe(false)
|
||||
expect(isDaemonCommand("npm install")).toBe(false)
|
||||
expect(isDaemonCommand("npm test")).toBe(false)
|
||||
expect(isDaemonCommand('echo "hello"')).toBe(false)
|
||||
expect(isDaemonCommand("cd /path/to/dir")).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("addDaemonPatterns", () => {
|
||||
it("should add string patterns", () => {
|
||||
expect(isDaemonCommand("my-custom-process")).toBe(false)
|
||||
addDaemonPatterns(["my-custom-process"])
|
||||
expect(isDaemonCommand("my-custom-process")).toBe(true)
|
||||
})
|
||||
|
||||
it("should add regex patterns", () => {
|
||||
expect(isDaemonCommand("custom-app --background")).toBe(false)
|
||||
addDaemonPatterns([/^custom-app\s+--background/])
|
||||
expect(isDaemonCommand("custom-app --background")).toBe(true)
|
||||
})
|
||||
|
||||
it("should handle multiple patterns", () => {
|
||||
expect(isDaemonCommand("my-process")).toBe(false)
|
||||
expect(isDaemonCommand("another-process")).toBe(false)
|
||||
|
||||
addDaemonPatterns(["my-process", "another-process"])
|
||||
|
||||
expect(isDaemonCommand("my-process")).toBe(true)
|
||||
expect(isDaemonCommand("another-process")).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("clearUserDaemonPatterns", () => {
|
||||
it("should clear user-defined patterns", () => {
|
||||
addDaemonPatterns(["my-custom-process"])
|
||||
expect(isDaemonCommand("my-custom-process")).toBe(true)
|
||||
|
||||
clearUserDaemonPatterns()
|
||||
expect(isDaemonCommand("my-custom-process")).toBe(false)
|
||||
})
|
||||
|
||||
it("should not affect built-in patterns", () => {
|
||||
addDaemonPatterns(["my-custom-process"])
|
||||
clearUserDaemonPatterns()
|
||||
|
||||
// Built-in patterns should still work
|
||||
expect(isDaemonCommand("npm start")).toBe(true)
|
||||
expect(isDaemonCommand("mvn spring-boot:run")).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getDaemonMessage", () => {
|
||||
it("should return a message for daemon processes", () => {
|
||||
const message = getDaemonMessage("npm start")
|
||||
expect(message).toContain("npm start")
|
||||
expect(message).toContain("long-running service/daemon")
|
||||
expect(message).toContain("background")
|
||||
})
|
||||
|
||||
it("should truncate long commands", () => {
|
||||
const longCommand =
|
||||
"java -Xmx4g -Xms2g -jar /very/long/path/to/application/with/many/options/application.jar --spring.profiles.active=production"
|
||||
const message = getDaemonMessage(longCommand)
|
||||
expect(message).toContain("...")
|
||||
})
|
||||
})
|
||||
|
||||
describe("getServiceType", () => {
|
||||
it("should identify Spring Boot applications", () => {
|
||||
expect(getServiceType("mvn spring-boot:run")).toBe("Spring Boot application")
|
||||
expect(getServiceType("gradle bootRun")).toBe("Spring Boot application")
|
||||
})
|
||||
|
||||
it("should identify Node.js applications", () => {
|
||||
expect(getServiceType("npm start")).toBe("Node.js application")
|
||||
expect(getServiceType("yarn dev")).toBe("Node.js application")
|
||||
expect(getServiceType("pnpm serve")).toBe("Node.js application")
|
||||
})
|
||||
|
||||
it("should identify Python applications", () => {
|
||||
expect(getServiceType("python app.py")).toBe("Python application")
|
||||
expect(getServiceType("flask run")).toBe("Python application")
|
||||
expect(getServiceType("python manage.py runserver")).toBe("Python application")
|
||||
})
|
||||
|
||||
it("should identify Rails applications", () => {
|
||||
expect(getServiceType("rails server")).toBe("Rails application")
|
||||
expect(getServiceType("rails s")).toBe("Rails application")
|
||||
})
|
||||
|
||||
it("should identify .NET applications", () => {
|
||||
expect(getServiceType("dotnet run")).toBe(".NET application")
|
||||
expect(getServiceType("dotnet watch")).toBe(".NET application")
|
||||
})
|
||||
|
||||
it("should identify Docker containers", () => {
|
||||
expect(getServiceType("docker run nginx")).toBe("Docker container")
|
||||
expect(getServiceType("docker-compose up")).toBe("Docker container")
|
||||
})
|
||||
|
||||
it("should identify PHP applications", () => {
|
||||
expect(getServiceType("php -S localhost:8000")).toBe("PHP application")
|
||||
expect(getServiceType("php artisan serve")).toBe("PHP application")
|
||||
})
|
||||
|
||||
it("should return generic application for unknown types", () => {
|
||||
expect(getServiceType("unknown-server start")).toBe("application")
|
||||
})
|
||||
})
|
||||
})
|
||||
166
src/utils/daemon-detector.ts
Normal file
166
src/utils/daemon-detector.ts
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
/**
|
||||
* Utility for detecting and managing daemon/long-running processes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Common patterns for daemon/service commands
|
||||
* These are commands that typically start long-running processes
|
||||
*/
|
||||
const DAEMON_COMMAND_PATTERNS = [
|
||||
// Java/Spring Boot patterns
|
||||
/^mvn\s+spring-boot:run/i,
|
||||
/^gradle\s+bootRun/i,
|
||||
/^java\s+-jar.*\.jar/i,
|
||||
/^java\s+.*\.(jar|war)$/i,
|
||||
/^spring\s+boot:run/i,
|
||||
|
||||
// Node.js patterns
|
||||
/^npm\s+(run\s+)?(start|dev|serve|watch)/i,
|
||||
/^yarn\s+(start|dev|serve|watch)/i,
|
||||
/^pnpm\s+(start|dev|serve|watch)/i,
|
||||
/^node\s+.*server/i,
|
||||
/^nodemon\s+/i,
|
||||
/^pm2\s+start/i,
|
||||
/^forever\s+start/i,
|
||||
|
||||
// Python patterns
|
||||
/^python3?\s+-m\s+http\.server/i,
|
||||
/^python3?\s+.*manage\.py\s+runserver/i, // Django
|
||||
/^flask\s+run/i,
|
||||
/^uvicorn\s+/i, // FastAPI
|
||||
/^gunicorn\s+/i,
|
||||
/^python3?\s+.*app\.py$/i,
|
||||
|
||||
// Ruby patterns
|
||||
/^rails\s+server/i,
|
||||
/^rails\s+s$/i,
|
||||
/^ruby\s+.*server/i,
|
||||
/^rackup\s+/i,
|
||||
|
||||
// PHP patterns
|
||||
/^php\s+-S\s+/i, // PHP built-in server
|
||||
/^php\s+artisan\s+serve/i, // Laravel
|
||||
|
||||
// .NET patterns
|
||||
/^dotnet\s+run/i,
|
||||
/^dotnet\s+watch/i,
|
||||
|
||||
// Go patterns
|
||||
/^go\s+run\s+.*\.go$/i,
|
||||
|
||||
// Docker patterns
|
||||
/^docker\s+run\s+(?!.*--rm)/i, // Docker run without --rm flag
|
||||
/^docker-compose\s+up(?!\s+.*-d)/i, // Docker compose up without -d flag
|
||||
|
||||
// Generic server patterns
|
||||
/\b(server|serve|watch|dev|start)\b.*$/i,
|
||||
/^.*\s+(--watch|--serve|--server)/i,
|
||||
]
|
||||
|
||||
/**
|
||||
* Additional patterns that can be configured by users
|
||||
*/
|
||||
let userDefinedPatterns: RegExp[] = []
|
||||
|
||||
/**
|
||||
* Check if a command is likely to start a daemon/long-running process
|
||||
* @param command The command to check
|
||||
* @returns true if the command is likely a daemon process
|
||||
*/
|
||||
export function isDaemonCommand(command: string): boolean {
|
||||
// Trim and normalize the command
|
||||
const normalizedCommand = command.trim()
|
||||
|
||||
// Check against built-in patterns
|
||||
for (const pattern of DAEMON_COMMAND_PATTERNS) {
|
||||
if (pattern.test(normalizedCommand)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check against user-defined patterns
|
||||
for (const pattern of userDefinedPatterns) {
|
||||
if (pattern.test(normalizedCommand)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Add user-defined daemon patterns
|
||||
* @param patterns Array of regex patterns or strings to match
|
||||
*/
|
||||
export function addDaemonPatterns(patterns: (string | RegExp)[]): void {
|
||||
const compiledPatterns = patterns.map((p) => {
|
||||
if (p instanceof RegExp) {
|
||||
return p
|
||||
}
|
||||
// Convert string to regex, escaping special characters
|
||||
const escaped = p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||
return new RegExp(escaped, "i")
|
||||
})
|
||||
|
||||
userDefinedPatterns = [...userDefinedPatterns, ...compiledPatterns]
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear user-defined daemon patterns
|
||||
*/
|
||||
export function clearUserDaemonPatterns(): void {
|
||||
userDefinedPatterns = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user-friendly message for daemon processes
|
||||
* @param command The daemon command
|
||||
* @returns A message explaining the daemon process handling
|
||||
*/
|
||||
export function getDaemonMessage(command: string): string {
|
||||
const shortCommand = command.length > 50 ? command.substring(0, 50) + "..." : command
|
||||
|
||||
return (
|
||||
`The command '${shortCommand}' appears to be starting a long-running service/daemon process. ` +
|
||||
`The process has been started in the background and will continue running. ` +
|
||||
`You can proceed with other tasks while this service runs. ` +
|
||||
`To stop the service, you may need to use Ctrl+C in the terminal or run the appropriate stop command.`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract service type from daemon command for better messaging
|
||||
* @param command The daemon command
|
||||
* @returns The type of service being started
|
||||
*/
|
||||
export function getServiceType(command: string): string {
|
||||
const normalizedCommand = command.toLowerCase()
|
||||
|
||||
if (normalizedCommand.includes("spring-boot") || normalizedCommand.includes("bootrun")) {
|
||||
return "Spring Boot application"
|
||||
}
|
||||
if (normalizedCommand.includes("npm") || normalizedCommand.includes("yarn") || normalizedCommand.includes("pnpm")) {
|
||||
return "Node.js application"
|
||||
}
|
||||
if (
|
||||
normalizedCommand.includes("python") ||
|
||||
normalizedCommand.includes("flask") ||
|
||||
normalizedCommand.includes("django")
|
||||
) {
|
||||
return "Python application"
|
||||
}
|
||||
if (normalizedCommand.includes("rails")) {
|
||||
return "Rails application"
|
||||
}
|
||||
if (normalizedCommand.includes("dotnet")) {
|
||||
return ".NET application"
|
||||
}
|
||||
if (normalizedCommand.includes("docker")) {
|
||||
return "Docker container"
|
||||
}
|
||||
if (normalizedCommand.includes("php")) {
|
||||
return "PHP application"
|
||||
}
|
||||
|
||||
return "application"
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue