Skip to content

Commit 7fa4cad

Browse files
committed
fix(mothership): address review findings and CI regressions
1 parent 4f08a0a commit 7fa4cad

54 files changed

Lines changed: 675 additions & 115 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/deploy-trigger-dev.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
set -euo pipefail
5353
: "${TRIGGER_ACCESS_TOKEN:?TRIGGER_ACCESS_TOKEN must be configured}"
5454
: "${TRIGGER_PROJECT_ID:?TRIGGER_PROJECT_ID must be configured}"
55-
bunx trigger.dev@4.5.12 deploy --env preview --branch dev-sim --skip-promotion
55+
bunx trigger.dev@4.5.16 deploy --env preview --branch dev-sim --skip-promotion
5656
5757
- name: Promote current dev preview
5858
working-directory: ./apps/sim
@@ -72,4 +72,4 @@ jobs:
7272
echo "::notice::Skipping superseded dev task version $VERSION"
7373
exit 0
7474
fi
75-
bunx trigger.dev@4.5.12 promote "$VERSION" --env preview --branch dev-sim
75+
bunx trigger.dev@4.5.16 promote "$VERSION" --env preview --branch dev-sim

‎apps/desktop/src/main/browser-agent/cdp.test.ts‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ describe('browser-agent CDP instrumentation', () => {
311311
}
312312
})
313313

314-
it.each(['complete', 'split', 'ambiguous'])(
314+
it.each(['complete', 'split', 'worker', 'detached', 'ambiguous'])(
315315
'routes OOPIF evaluation through its session (%s tree)',
316316
async (treeKind) => {
317317
const contents = new WebContentsView().webContents
@@ -338,9 +338,20 @@ describe('browser-agent CDP instrumentation', () => {
338338
{ autoAttach: true, waitForDebuggerOnStart: false, flatten: true },
339339
'child-session'
340340
)
341+
if (treeKind === 'worker' || treeKind === 'detached') {
342+
listener?.({}, 'Target.attachedToTarget', {
343+
sessionId: 'unavailable-session',
344+
targetInfo: {
345+
targetId: 'unavailable',
346+
type: treeKind === 'worker' ? 'worker' : 'iframe',
347+
},
348+
})
349+
}
341350
vi.mocked(contents.debugger.sendCommand).mockClear()
342351
vi.mocked(contents.debugger.sendCommand).mockImplementation((method, _params, sessionId) => {
343352
if (method === 'Page.getFrameTree') {
353+
if (sessionId === 'unavailable-session')
354+
return Promise.reject(new Error('Target unavailable'))
344355
return Promise.resolve({
345356
frameTree:
346357
treeKind === 'complete'

‎apps/desktop/src/main/browser-agent/cdp.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ export async function evaluateInIsolatedFrame(
308308
if (!protocolFrame) {
309309
/** Chromium omits out-of-process frames from the root target's tree. */
310310
const childSessions = [...(childSessionsByContents.get(contents)?.values() ?? [])]
311-
const trees = await Promise.all(
311+
const results = await Promise.allSettled(
312312
childSessions.map(async (sessionId) => {
313313
const result = await send<{ frameTree?: ProtocolFrameTree }>(
314314
contents,
@@ -319,6 +319,9 @@ export async function evaluateInIsolatedFrame(
319319
return result.frameTree
320320
})
321321
)
322+
const trees = results.flatMap((result) =>
323+
result.status === 'fulfilled' && result.value ? [result.value] : []
324+
)
322325
const nodes = new Map<string, ProtocolFrameTree>()
323326
const visit = (tree: ProtocolFrameTree) => {
324327
nodes.set(tree.frame.id, tree)

‎apps/desktop/src/main/browser-agent/registry.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { Session, WebContents } from 'electron'
1010
* navigation time, so the post-construction registration races nothing.
1111
*/
1212
const agentContents = new WeakSet<WebContents>()
13+
const agentSessions = new WeakSet<Session>()
1314
const appOrigins = new WeakMap<WebContents, string>()
1415
const navigations = new WeakMap<WebContents, (url: string, method: string) => boolean>()
1516
const permissions = new WeakMap<WebContents, BrowserPermissionHandlers>()
@@ -25,10 +26,16 @@ export function registerAgentWebContents(
2526
handlers?: BrowserPermissionHandlers
2627
): void {
2728
agentContents.add(contents)
29+
agentSessions.add(contents.session)
2830
if (appOrigin) appOrigins.set(contents, appOrigin)
2931
if (handlers) permissions.set(contents, handlers)
3032
}
3133

34+
/** Worker requests can outlive their originating tab and omit webContents. */
35+
export function hasAgentSession(session: Session): boolean {
36+
return agentSessions.has(session)
37+
}
38+
3239
export function isAgentWebContents(contents: WebContents): boolean {
3340
return agentContents.has(contents)
3441
}

‎apps/desktop/src/main/telemetry-policy.test.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,13 @@ describe('attachTelemetryPolicy', () => {
6262
listener(request, callback)
6363
expect(requestPolicy.handleBrowserRequest).toHaveBeenCalledExactlyOnceWith(request, callback)
6464
expect(callback).not.toHaveBeenCalled()
65+
requestPolicy.handleBrowserRequest.mockClear()
66+
const workerRequest = { ...request, webContents: undefined, resourceType: 'other' as const }
67+
listener(workerRequest, callback)
68+
expect(requestPolicy.handleBrowserRequest).toHaveBeenCalledExactlyOnceWith(
69+
workerRequest,
70+
callback
71+
)
72+
expect(callback).not.toHaveBeenCalled()
6573
})
6674
})

‎apps/desktop/src/main/telemetry-policy.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import type { Session } from 'electron'
3-
import { isAgentWebContents } from '@/main/browser-agent/registry'
3+
import { hasAgentSession, isAgentWebContents } from '@/main/browser-agent/registry'
44
import { handleBrowserRequest } from '@/main/browser-agent/request-policy'
55
import { matchesHostList } from '@/main/navigation'
66

@@ -40,7 +40,9 @@ export function attachTelemetryPolicy(session: Session, enabled: boolean): void
4040
session.webRequest.onBeforeRequest((details, callback) => {
4141
if (enabled && shouldBlockRequest(details.url)) {
4242
callback({ cancel: true })
43-
} else if (details.webContents && isAgentWebContents(details.webContents)) {
43+
} else if (
44+
details.webContents ? isAgentWebContents(details.webContents) : hasAgentSession(session)
45+
) {
4446
handleBrowserRequest(details, callback)
4547
} else {
4648
callback({ cancel: false })

‎apps/sim/app/api/custom-blocks/[id]/route.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ import {
1212
deleteCustomBlockSettings,
1313
updateCustomBlockSettings,
1414
} from '@/lib/workflows/custom-blocks/application/settings'
15-
import { customBlockError } from '@/app/api/custom-blocks/errors'
15+
import { customBlockResourceErrorPolicy } from '@/app/api/custom-blocks/errors'
1616

1717
export const PATCH = defineInternalJsonRoute({
1818
contract: updateCustomBlockContract,
1919
auth: internalSessionAuth,
2020
operation: customBlockSettingsOperations.update,
2121
rateLimit: internalRateLimits.none({ reason: 'Preserve existing custom block update policy.' }),
22-
errorPolicy: { project: customBlockError },
22+
errorPolicy: customBlockResourceErrorPolicy,
2323
mapInput: ({ params, body }) => ({ id: params.id, patch: body }),
2424
useCase: updateCustomBlockSettings,
2525
})
@@ -29,7 +29,7 @@ export const DELETE = defineInternalJsonRoute({
2929
auth: internalSessionAuth,
3030
operation: customBlockSettingsOperations.delete,
3131
rateLimit: internalRateLimits.none({ reason: 'Preserve existing custom block deletion policy.' }),
32-
errorPolicy: { project: customBlockError },
32+
errorPolicy: customBlockResourceErrorPolicy,
3333
mapInput: ({ params }) => params,
3434
useCase: deleteCustomBlockSettings,
3535
present: () => ({ success: true as const }),

‎apps/sim/app/api/custom-blocks/[id]/usages/route.test.ts‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ const { mockHasWorkspaceAdminAccess, mockOperations } = vi.hoisted(() => ({
1717
vi.mock('@sim/platform-authz/workspace', async (importOriginal) => ({
1818
...(await importOriginal<typeof import('@sim/platform-authz/workspace')>()),
1919
resolveEffectiveWorkspacePermission: async (...args: unknown[]) =>
20-
(await mockHasWorkspaceAdminAccess(...args)) ? 'admin' : 'read',
20+
(await mockHasWorkspaceAdminAccess(...args)) === null
21+
? null
22+
: (await mockHasWorkspaceAdminAccess(...args))
23+
? 'admin'
24+
: 'read',
2125
}))
2226
vi.mock('@/lib/workspaces/application/workspace-context', () => ({
2327
resolveActiveWorkspaceApplicationContext: async (workspaceId: string) => ({
@@ -68,6 +72,13 @@ describe('GET /api/custom-blocks/[id]/usages', () => {
6872
expect(response.status).toBe(404)
6973
})
7074

75+
it('conceals a block in an inaccessible workspace', async () => {
76+
mockHasWorkspaceAdminAccess.mockResolvedValue(null)
77+
const response = await callRoute()
78+
expect(response.status).toBe(404)
79+
expect(mockOperations.getCustomBlockUsageCounts).not.toHaveBeenCalled()
80+
})
81+
7182
it('returns 403 for a non-admin of the source workspace', async () => {
7283
mockHasWorkspaceAdminAccess.mockResolvedValue(false)
7384
const response = await callRoute()

‎apps/sim/app/api/custom-blocks/[id]/usages/route.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
customBlockSettingsOperations,
99
readCustomBlockUsages,
1010
} from '@/lib/workflows/custom-blocks/application/settings'
11-
import { customBlockError } from '@/app/api/custom-blocks/errors'
11+
import { customBlockResourceErrorPolicy } from '@/app/api/custom-blocks/errors'
1212

1313
export const GET = defineInternalJsonRoute({
1414
contract: getCustomBlockUsageCountsContract,
@@ -17,7 +17,7 @@ export const GET = defineInternalJsonRoute({
1717
rateLimit: internalRateLimits.none({
1818
reason: 'Preserve existing custom block usage read policy.',
1919
}),
20-
errorPolicy: { project: customBlockError },
20+
errorPolicy: customBlockResourceErrorPolicy,
2121
mapInput: ({ params }) => params,
2222
useCase: readCustomBlockUsages,
2323
})

‎apps/sim/app/api/custom-blocks/errors.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { internalErrorResponse } from '@/lib/api/server/routes'
1+
import {
2+
createInternalResourceConcealmentPolicy,
3+
internalErrorResponse,
4+
} from '@/lib/api/server/routes'
25
import { InternalUnauthenticatedError } from '@/lib/api/server/routes/internal-json-route'
36
import {
47
InsufficientWorkspacePermissionsError,
@@ -23,3 +26,8 @@ export function customBlockError(error: unknown, read = false) {
2326
return internalErrorResponse(statusForOrchestrationError(error.code), { error: error.message })
2427
return null
2528
}
29+
30+
export const customBlockResourceErrorPolicy = createInternalResourceConcealmentPolicy({
31+
base: { project: customBlockError },
32+
notFoundMessage: 'Custom block not found',
33+
})

0 commit comments

Comments
 (0)