fix: remove duplicate pattern matching logic from ServiceManager

- Remove duplicate readyPattern detection from ServiceManager.onLine callback
- Make ExecuteCommandTool.waitForPattern the single source of truth for readiness detection
- Add status change notification to waitForPattern to ensure proper state updates
- Make ServiceManager.notifyStatusChange public so it can be called from waitForPattern
- Update tests to reflect new behavior where ServiceManager only collects logs
- Fixes race condition and maintenance burden from duplicate logic
This commit is contained in:
Roo Code 2025-11-16 11:11:13 +00:00
parent c89ff1d329
commit f6c32b495e
2 changed files with 26 additions and 19 deletions

View file

@ -77,7 +77,7 @@ export class ServiceManager {
healthCheckIntervalMs: options.healthCheckIntervalMs || 1000,
}
// Set up callbacks to collect logs and detect readiness
// Set up callbacks to collect logs
const callbacks: RooTerminalCallbacks = {
onLine: (line: string, process: RooTerminalProcess) => {
// Add to logs
@ -85,20 +85,6 @@ export class ServiceManager {
if (serviceHandle.logs.length > (serviceHandle.maxLogLines || 1000)) {
serviceHandle.logs.shift() // Remove oldest log
}
// If status is starting, check if readyPattern matches
if (serviceHandle.status === "starting" && serviceHandle.readyPattern) {
const regex =
typeof serviceHandle.readyPattern === "string"
? new RegExp(serviceHandle.readyPattern, "i")
: serviceHandle.readyPattern
if (regex.test(line)) {
serviceHandle.status = "ready"
serviceHandle.readyAt = Date.now()
this.notifyStatusChange(serviceHandle)
}
}
},
onCompleted: () => {
// Service should not "complete", if it completes it means the process exited

View file

@ -149,7 +149,7 @@ describe("ServiceManager", () => {
expect(logs.length).toBeLessThanOrEqual(5)
})
it("should detect service ready via readyPattern", async () => {
it("should store readyPattern but not automatically detect it (detection happens in ExecuteCommandTool)", async () => {
const serviceHandle = await ServiceManager.startService("npm run dev", "/test/workspace", {
readyPattern: "Local:.*http://localhost",
})
@ -162,9 +162,22 @@ describe("ServiceManager", () => {
mockCallbacks.onLine("Local: http://localhost:3000", mockProcess)
}
// Wait for status update
// Wait a bit
await new Promise((resolve) => setTimeout(resolve, 50))
// ServiceManager should NOT automatically update status - it only collects logs
// The status update happens in ExecuteCommandTool.waitForPattern
expect(serviceHandle.status).toBe("starting")
// But logs should be collected
const logs = ServiceManager.getServiceLogs(serviceHandle.serviceId)
expect(logs).toContain("Local: http://localhost:3000")
// Manually update status (simulating what ExecuteCommandTool.waitForPattern does)
serviceHandle.status = "ready"
serviceHandle.readyAt = Date.now()
ServiceManager.notifyStatusChange(serviceHandle)
expect(serviceHandle.status).toBe("ready")
expect(serviceHandle.readyAt).toBeDefined()
})
@ -362,7 +375,7 @@ describe("ServiceManager", () => {
})
describe("Service state machine", () => {
it("should correctly transition states: pending -> starting -> ready", async () => {
it("should correctly transition states: pending -> starting (ready detection happens in ExecuteCommandTool)", async () => {
const serviceHandle = await ServiceManager.startService("npm run dev", "/test/workspace", {
readyPattern: "Local:.*http://localhost",
})
@ -373,12 +386,20 @@ describe("ServiceManager", () => {
await new Promise((resolve) => setTimeout(resolve, 50))
expect(serviceHandle.status).toBe("starting")
// Trigger ready pattern
// Trigger ready pattern - ServiceManager only collects logs
if (mockCallbacks?.onLine) {
mockCallbacks.onLine("Local: http://localhost:3000", mockProcess)
}
await new Promise((resolve) => setTimeout(resolve, 50))
// Status should still be starting - ExecuteCommandTool.waitForPattern handles ready detection
expect(serviceHandle.status).toBe("starting")
// Manually transition to ready (simulating ExecuteCommandTool.waitForPattern)
serviceHandle.status = "ready"
serviceHandle.readyAt = Date.now()
ServiceManager.notifyStatusChange(serviceHandle)
expect(serviceHandle.status).toBe("ready")
})