Skip to content

Commit ecc8ede

Browse files
committed
fix: address review feedback for PR #505
1 parent 8853e1f commit ecc8ede

File tree

7 files changed

+115
-23
lines changed

7 files changed

+115
-23
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
## Review Comment Evaluation
2+
3+
### Comment 1: [packages/browseros-agent/apps/server/src/api/services/sdk/browser.ts:128] — greptile-apps[bot]
4+
> Silent fall-through when `windowId` navigation fails completely.
5+
**Verdict: ACCEPT**
6+
**Reasoning:** The comment identifies a real behavioral regression. If the caller specifies `windowId`, `newPage(url, { windowId })` fails, and there is no fallback page, the current code falls through to the generic `newPage(url)` path and silently drops the window constraint.
7+
**Action:** Preserve the failure instead of silently ignoring the requested window. Re-throw the underlying window-targeted error when no fallback page exists, and add a focused unit test for that path.
8+
9+
### Comment 2: [packages/browseros-agent/apps/server/tests/api/routes/health.test.ts:12] — greptile summary
10+
> Test asserts default fallback, not real CDP connectivity.
11+
**Verdict: ACCEPT**
12+
**Reasoning:** This is a valid test-quality concern even though it appeared in the summary rather than as a standalone inline comment. The existing test only exercised the `?? true` fallback and would miss a regression in the actual `browser.isCdpConnected()` path.
13+
**Action:** Update the route test to pass a mock browser and cover both connected and disconnected CDP states.

packages/browseros-agent/apps/eval/scripts/debug-mcp.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@ interface TestResult {
1616
error?: string
1717
}
1818

19+
interface TextContentItem {
20+
type: string
21+
text?: string
22+
}
23+
24+
interface StructuredWindowResult {
25+
windowId?: number
26+
tabId?: number
27+
}
28+
29+
interface McpToolResult {
30+
isError?: boolean
31+
content?: TextContentItem[]
32+
structuredContent?: StructuredWindowResult
33+
}
34+
1935
const results: TestResult[] = []
2036

2137
async function checkHealth(): Promise<boolean> {
@@ -67,13 +83,15 @@ async function callMcpTool(
6783
),
6884
)
6985

70-
const result = await Promise.race([toolPromise, timeoutPromise])
86+
const result = (await Promise.race([
87+
toolPromise,
88+
timeoutPromise,
89+
])) as McpToolResult
7190
const duration = Date.now() - start
7291

73-
if ((result as any).isError) {
92+
if (result.isError) {
7493
const errorText =
75-
(result as any).content?.find((c: any) => c.type === 'text')?.text ||
76-
'Unknown error'
94+
result.content?.find((c) => c.type === 'text')?.text || 'Unknown error'
7795
return { success: false, error: errorText, duration }
7896
}
7997

@@ -161,15 +179,16 @@ async function main() {
161179
})
162180
if (!res.success) throw new Error(res.error)
163181

164-
const structured = (res.result as any)?.structuredContent
182+
const structured = res.result as StructuredWindowResult | undefined
165183
windowId = structured?.windowId
166184
tabId = structured?.tabId
167185

168186
if (!windowId || !tabId) {
169187
// Try parsing from text
170188
const text =
171-
(res.result as any)?.content?.find((c: any) => c.type === 'text')
172-
?.text || ''
189+
(res.result as McpToolResult | undefined)?.content?.find(
190+
(c) => c.type === 'text',
191+
)?.text || ''
173192
const windowMatch = text.match(/window\s+(\d+)/i)
174193
const tabMatch = text.match(/tab\s+(?:ID:\s*)?(\d+)/i)
175194
if (windowMatch) windowId = parseInt(windowMatch[1], 10)

packages/browseros-agent/apps/eval/scripts/test-lifecycle.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ const MCP_URL = `http://127.0.0.1:${EVAL_PORTS.server}/mcp`
2828

2929
let currentServerPid: number | null = null
3030

31+
interface TextContentItem {
32+
type: string
33+
text?: string
34+
}
35+
36+
interface McpToolResult {
37+
isError?: boolean
38+
content?: TextContentItem[]
39+
}
40+
3141
// ============================================================================
3242
// Utility Functions (same as parallel-executor)
3343
// ============================================================================
@@ -193,7 +203,7 @@ async function callMcpTool(
193203
name: string,
194204
args: Record<string, unknown> = {},
195205
timeoutMs = 60000,
196-
): Promise<{ success: boolean; result?: any; error?: string }> {
206+
): Promise<{ success: boolean; result?: McpToolResult; error?: string }> {
197207
const client = new Client({ name: 'lifecycle-test', version: '1.0.0' })
198208
const transport = new StreamableHTTPClientTransport(new URL(MCP_URL))
199209

@@ -206,12 +216,14 @@ async function callMcpTool(
206216
timeoutMs,
207217
),
208218
)
209-
const result = await Promise.race([toolPromise, timeoutPromise])
219+
const result = (await Promise.race([
220+
toolPromise,
221+
timeoutPromise,
222+
])) as McpToolResult
210223

211-
if ((result as any).isError) {
224+
if (result.isError) {
212225
const errorText =
213-
(result as any).content?.find((c: any) => c.type === 'text')?.text ||
214-
'Unknown error'
226+
result.content?.find((c) => c.type === 'text')?.text || 'Unknown error'
215227
return { success: false, error: errorText }
216228
}
217229
return { success: true, result }

packages/browseros-agent/apps/eval/src/agents/single-agent.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { ResolvedAgentConfig } from '@browseros/server/agent/types'
44
import { Browser } from '@browseros/server/browser'
55
import { CdpBackend } from '@browseros/server/browser/backends/cdp'
66
import { registry } from '@browseros/server/tools/registry'
7+
import type { UIMessageStreamEvent } from '@browseros/shared/schemas/ui-stream'
78
import { DEFAULT_TIMEOUT_MS } from '../constants'
89
import type { EvalConfig, TaskMetadata } from '../types'
910
import { resolveProviderConfig } from '../utils/resolve-provider-config'
@@ -114,38 +115,44 @@ export class SingleAgentEvaluator implements AgentEvaluator {
114115
onStepFinish: async ({ toolCalls, toolResults, text }) => {
115116
if (toolCalls) {
116117
for (const tc of toolCalls) {
117-
const inputEvent = {
118+
const inputEvent: UIMessageStreamEvent = {
118119
type: 'tool-input-available',
119120
toolCallId: tc.toolCallId,
120121
toolName: tc.toolName,
121122
input: tc.input,
122-
} as any
123+
}
123124
await capture.messageLogger.logStreamEvent(inputEvent)
124125
capture.emitEvent(task.query_id, inputEvent)
125126
}
126127
}
127128

128129
if (toolResults) {
129130
for (const tr of toolResults) {
130-
const outputEvent = {
131+
const outputEvent: UIMessageStreamEvent = {
131132
type: 'tool-output-available',
132133
toolCallId: tr.toolCallId,
133134
output: tr.output,
134-
} as any
135+
}
135136
await capture.messageLogger.logStreamEvent(outputEvent)
136137
capture.emitEvent(task.query_id, outputEvent)
137138
}
138139
}
139140

140141
if (text) {
141142
const textId = randomUUID()
142-
const startEvent = { type: 'text-start', id: textId } as any
143-
const deltaEvent = {
143+
const startEvent: UIMessageStreamEvent = {
144+
type: 'text-start',
145+
id: textId,
146+
}
147+
const deltaEvent: UIMessageStreamEvent = {
144148
type: 'text-delta',
145149
id: textId,
146150
delta: text,
147-
} as any
148-
const endEvent = { type: 'text-end', id: textId } as any
151+
}
152+
const endEvent: UIMessageStreamEvent = {
153+
type: 'text-end',
154+
id: textId,
155+
}
149156
await capture.messageLogger.logStreamEvent(startEvent)
150157
await capture.messageLogger.logStreamEvent(deltaEvent)
151158
await capture.messageLogger.logStreamEvent(endEvent)

packages/browseros-agent/apps/server/src/api/services/sdk/browser.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export class BrowserService {
125125
tabId: createdPage.tabId,
126126
windowId: createdPage.windowId ?? windowId,
127127
}
128-
} catch {
128+
} catch (error) {
129129
const fallbackPage = await this.getFallbackPage()
130130
if (fallbackPage) {
131131
await this.browser.goto(fallbackPage.pageId, url)
@@ -134,6 +134,13 @@ export class BrowserService {
134134
windowId: fallbackPage.windowId ?? 0,
135135
}
136136
}
137+
throw error instanceof SdkError
138+
? error
139+
: new SdkError(
140+
error instanceof Error
141+
? error.message
142+
: 'Failed to navigate in specified window',
143+
)
137144
}
138145
}
139146

packages/browseros-agent/apps/server/tests/api/routes/health.test.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,25 @@ import assert from 'node:assert'
99
import { createHealthRoute } from '../../../src/api/routes/health'
1010

1111
describe('createHealthRoute', () => {
12-
it('returns status ok', async () => {
13-
const route = createHealthRoute()
12+
it('returns status ok with connected CDP state', async () => {
13+
const route = createHealthRoute({
14+
browser: { isCdpConnected: () => true } as never,
15+
})
1416
const response = await route.request('/')
1517

1618
assert.strictEqual(response.status, 200)
1719
const body = await response.json()
1820
assert.deepStrictEqual(body, { status: 'ok', cdpConnected: true })
1921
})
22+
23+
it('returns disconnected CDP state when browser is disconnected', async () => {
24+
const route = createHealthRoute({
25+
browser: { isCdpConnected: () => false } as never,
26+
})
27+
const response = await route.request('/')
28+
29+
assert.strictEqual(response.status, 200)
30+
const body = await response.json()
31+
assert.deepStrictEqual(body, { status: 'ok', cdpConnected: false })
32+
})
2033
})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { describe, expect, it } from 'bun:test'
2+
import { BrowserService } from '../../../../src/api/services/sdk/browser'
3+
4+
describe('BrowserService.navigate', () => {
5+
it('throws when a window-targeted navigation cannot create or find any page', async () => {
6+
const browser = {
7+
listPages: async () => [],
8+
getActivePage: async () => null,
9+
newPage: async () => {
10+
throw new Error('Window not found')
11+
},
12+
goto: async () => {},
13+
}
14+
15+
const service = new BrowserService(browser as never)
16+
17+
await expect(
18+
service.navigate('https://example.com', undefined, 12345),
19+
).rejects.toThrow('Window not found')
20+
})
21+
})

0 commit comments

Comments
 (0)