Skip to content

Commit 42f1c5f

Browse files
committed
fix(plan): resolve review findings and trigger initialization
1 parent 28abfc7 commit 42f1c5f

317 files changed

Lines changed: 31681 additions & 487 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.

‎.agents/skills/add-trigger/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export function build{Service}Outputs(): Record<string, TriggerOutput> {
108108

109109
```typescript
110110
import { {Service}Icon } from '@/components/icons'
111-
import { buildTriggerSubBlocks } from '@/triggers'
111+
import { buildTriggerSubBlocks } from '@/triggers/subblocks'
112112
import { build{Service}ExtraFields, build{Service}Outputs, {service}SetupInstructions, {service}TriggerOptions } from '@/triggers/{service}/utils'
113113
import type { TriggerConfig } from '@/triggers/types'
114114

‎apps/desktop/e2e/local-files.spec.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,17 @@ import { ToastProvider } from '@sim/emcn';
6363
import { AppRouterContext } from 'next/dist/shared/lib/app-router-context.shared-runtime';
6464
import { PathParamsContext } from 'next/dist/shared/lib/hooks-client-context.shared-runtime';
6565
import { Desktop } from '@/app/workspace/[workspaceId]/settings/components/desktop/desktop';
66+
import { FeatureFlagsProvider } from '@/app/workspace/[workspaceId]/providers/feature-flags-provider';
6667
import { SettingsHeaderProvider, SettingsHeaderShell } from '@/components/settings/settings-header';
6768
import { SettingsSectionProvider } from '@/components/settings/settings-panel';
6869
const router = { bfcacheId: 'fixture', back: () => history.back(), forward: () => history.forward(), refresh: () => location.reload(), push: url => location.assign(url), replace: url => location.replace(url), prefetch: () => {} };
6970
createRoot(document.getElementById('settings')).render(
7071
<AppRouterContext.Provider value={router}>
7172
<PathParamsContext.Provider value={{ workspaceId: 'fixture' }}>
7273
<ToastProvider><SettingsHeaderProvider><SettingsHeaderShell>
73-
<SettingsSectionProvider plane='workspace' section='desktop'><Desktop /></SettingsSectionProvider>
74+
<FeatureFlagsProvider flags={{ dashboards: false, 'workflow-tests': false, 'mothership-model-selector': false, 'mothership-plan-mode': false }}>
75+
<SettingsSectionProvider plane='workspace' section='desktop'><Desktop /></SettingsSectionProvider>
76+
</FeatureFlagsProvider>
7477
</SettingsHeaderShell></SettingsHeaderProvider></ToastProvider>
7578
</PathParamsContext.Provider>
7679
</AppRouterContext.Provider>

‎apps/desktop/native/computer-use/ComputerUse.swift‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,12 +389,12 @@ func discoverApps() throws -> [[String: Any]] {
389389
var entries: [String: [String: Any]] = [:]
390390
let roots = [URL(fileURLWithPath: "/Applications"), URL(fileURLWithPath: "/System/Applications"), FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Applications")]
391391
var inspected = 0
392-
for root in roots {
392+
scanRoots: for root in roots {
393393
guard let enumerator = FileManager.default.enumerator(at: root, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles]) else { continue }
394394
for case let url as URL in enumerator {
395395
try checkCancellation()
396+
if inspected >= 5000 || entries.count >= 1000 { break scanRoots }
396397
inspected += 1
397-
if inspected > 5000 || entries.count >= 1000 { break }
398398
if url.pathExtension == "app" {
399399
enumerator.skipDescendants()
400400
guard let bundle = Bundle(url: url), let id = bundle.bundleIdentifier, id.utf8.count <= 255 else { continue }

‎apps/desktop/src/main/computer-use/service.test.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -470,8 +470,10 @@ describe('native computer use authority and lifecycle', () => {
470470
expect(approveApp).toHaveBeenCalledTimes(3)
471471
})
472472

473-
it('Stop cancels a tool still waiting on server authorization', async () => {
474-
const { service, native } = setup()
473+
it('Stop cancels pending authorization without invalidating another chat observation', async () => {
474+
const { service, native, state, click } = setup()
475+
await state('unrelated-chat')
476+
native.request.mockClear()
475477
let finish: (value: { scopeId: string; input: unknown }) => void = () => {}
476478
const authorize = vi.fn(
477479
() =>
@@ -485,6 +487,7 @@ describe('native computer use authority and lifecycle', () => {
485487
finish({ scopeId: 'chat', input: { action: 'list_apps' } })
486488
await rejected
487489
expect(native.request).not.toHaveBeenCalled()
490+
await expect(click('s1', 'unrelated-chat')).resolves.toMatchObject({ kind: 'action' })
488491
})
489492

490493
it('rejects concurrent duplicate claims before contacting the server again', async () => {

‎apps/desktop/src/main/computer-use/service.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,10 @@ export class ComputerUseService {
144144
for (const work of this.queued.values()) {
145145
if (!toolCallId || work.toolCallId === toolCallId) work.controller.abort()
146146
}
147-
if (!toolCallId || this.active?.toolCallId === toolCallId) this.deps.native.stop()
148-
this.invalidateSnapshots()
147+
if (!toolCallId || this.active?.toolCallId === toolCallId) {
148+
this.deps.native.stop()
149+
this.invalidateSnapshots()
150+
}
149151
}
150152

151153
/** Register Stop before awaiting the server's one-shot tool authorization. */

‎apps/sim/app/o/[organizationId]/benchmark/benchmark.test.tsx‎

Lines changed: 89 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -16,82 +16,95 @@ vi.mock('@/app/o/[organizationId]/benchmark/components', () => ({
1616
CreateBenchmark: () => null,
1717
}))
1818

19-
it('preserves cached benchmarks through a failed next page and retries without losing the first page', async () => {
20-
authClientMockFns.mockUseSession.mockReturnValue({
21-
data: { user: { id: 'owner' } },
22-
isPending: false,
23-
error: null,
24-
})
25-
vi.useFakeTimers()
26-
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true)
27-
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } })
28-
const container = document.createElement('div')
29-
document.body.append(container)
30-
const root = createRoot(container)
31-
const record = (id: string) => ({
32-
id,
33-
name: id,
34-
organizationId: 'org',
35-
userId: 'owner',
36-
runAsUserId: 'owner',
37-
sourceWorkspaceId: 'workspace',
38-
version: 1,
39-
runningStage: null,
40-
leaseExpiresAt: null,
41-
attemptId: null,
42-
plannerChatId: null,
43-
error: null,
44-
createdAt: '2026-01-01T00:00:00.000Z',
45-
updatedAt: '2026-01-01T00:00:00.000Z',
46-
})
47-
const fetcher = vi
48-
.fn()
49-
.mockResolvedValueOnce(
50-
jsonResponse({ benchmarks: [record('First benchmark')], nextCursor: 'next' })
51-
)
52-
.mockResolvedValueOnce(jsonResponse({ error: 'Temporary failure' }, 503))
53-
.mockResolvedValueOnce(
54-
jsonResponse({ benchmarks: [record('Older benchmark')], nextCursor: null })
55-
)
56-
vi.stubGlobal('fetch', fetcher)
57-
const settle = async (action: () => void) => {
58-
await act(async () => action())
59-
await act(async () => {
60-
await flushMicrotasks()
61-
await vi.advanceTimersByTimeAsync(1)
19+
it.each([false, true])(
20+
'recovers initial failure %s and paginates without losing cached benchmarks',
21+
async (initialFailure) => {
22+
authClientMockFns.mockUseSession.mockReturnValue({
23+
data: { user: { id: 'owner' } },
24+
isPending: false,
25+
error: null,
6226
})
63-
}
64-
const openButtons = () =>
65-
[...container.querySelectorAll<HTMLButtonElement>('button')].filter(
66-
(button) => button.textContent === 'Open'
67-
)
68-
const olderButton = () =>
69-
[...container.querySelectorAll<HTMLButtonElement>('button')].find((button) =>
70-
/older benchmarks/i.test(button.textContent ?? '')
71-
)
72-
try {
73-
await settle(() =>
74-
root.render(
75-
<QueryClientProvider client={client}>
76-
<NuqsTestingAdapter hasMemory>
77-
<Benchmark organizationId='org' runAsUserId='owner' canPlan />
78-
</NuqsTestingAdapter>
79-
</QueryClientProvider>
27+
vi.useFakeTimers()
28+
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true)
29+
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } })
30+
const container = document.createElement('div')
31+
document.body.append(container)
32+
const root = createRoot(container)
33+
const record = (id: string) => ({
34+
id,
35+
name: id,
36+
organizationId: 'org',
37+
userId: 'owner',
38+
runAsUserId: 'owner',
39+
sourceWorkspaceId: 'workspace',
40+
version: 1,
41+
runningStage: null,
42+
leaseExpiresAt: null,
43+
attemptId: null,
44+
plannerChatId: null,
45+
error: null,
46+
createdAt: '2026-01-01T00:00:00.000Z',
47+
updatedAt: '2026-01-01T00:00:00.000Z',
48+
})
49+
const fetcher = vi.fn()
50+
if (initialFailure)
51+
fetcher.mockResolvedValueOnce(jsonResponse({ error: 'Temporary failure' }, 503))
52+
fetcher
53+
.mockResolvedValueOnce(
54+
jsonResponse({ benchmarks: [record('First benchmark')], nextCursor: 'next' })
55+
)
56+
.mockResolvedValueOnce(jsonResponse({ error: 'Temporary failure' }, 503))
57+
.mockResolvedValueOnce(
58+
jsonResponse({ benchmarks: [record('Older benchmark')], nextCursor: null })
59+
)
60+
vi.stubGlobal('fetch', fetcher)
61+
const settle = async (action: () => void) => {
62+
await act(async () => action())
63+
await act(async () => {
64+
await flushMicrotasks()
65+
await vi.advanceTimersByTimeAsync(1)
66+
})
67+
}
68+
const openButtons = () =>
69+
[...container.querySelectorAll<HTMLButtonElement>('button')].filter(
70+
(button) => button.textContent === 'Open'
71+
)
72+
const olderButton = () =>
73+
[...container.querySelectorAll<HTMLButtonElement>('button')].find((button) =>
74+
/older benchmarks/i.test(button.textContent ?? '')
75+
)
76+
try {
77+
await settle(() =>
78+
root.render(
79+
<QueryClientProvider client={client}>
80+
<NuqsTestingAdapter hasMemory>
81+
<Benchmark organizationId='org' runAsUserId='owner' canPlan />
82+
</NuqsTestingAdapter>
83+
</QueryClientProvider>
84+
)
8085
)
81-
)
82-
expect(openButtons()).toHaveLength(1)
83-
await settle(() => olderButton()?.click())
84-
expect(container.querySelector('[role="alert"]')).not.toBeNull()
85-
expect(openButtons()).toHaveLength(1)
86-
expect(olderButton()?.disabled).toBe(false)
87-
await settle(() => olderButton()?.click())
88-
expect(openButtons()).toHaveLength(2)
89-
expect(container.querySelector('[role="alert"]')).toBeNull()
90-
expect(olderButton()).toBeUndefined()
91-
} finally {
92-
await act(async () => root.unmount())
93-
client.clear()
94-
container.remove()
95-
vi.useRealTimers()
86+
if (initialFailure) {
87+
expect(openButtons()).toHaveLength(0)
88+
await settle(() =>
89+
[...container.querySelectorAll<HTMLButtonElement>('button')]
90+
.find((button) => button.textContent === 'Retry')
91+
?.click()
92+
)
93+
}
94+
expect(openButtons()).toHaveLength(1)
95+
await settle(() => olderButton()?.click())
96+
expect(container.querySelector('[role="alert"]')).not.toBeNull()
97+
expect(openButtons()).toHaveLength(1)
98+
expect(olderButton()?.disabled).toBe(false)
99+
await settle(() => olderButton()?.click())
100+
expect(openButtons()).toHaveLength(2)
101+
expect(container.querySelector('[role="alert"]')).toBeNull()
102+
expect(olderButton()).toBeUndefined()
103+
} finally {
104+
await act(async () => root.unmount())
105+
client.clear()
106+
container.remove()
107+
vi.useRealTimers()
108+
}
96109
}
97-
})
110+
)

‎apps/sim/app/o/[organizationId]/benchmark/benchmark.tsx‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,16 @@ export function Benchmark({ organizationId, canPlan, runAsUserId }: BenchmarkPro
6666
) : (
6767
<>
6868
{benchmarks.error && (
69-
<p role='alert' className='text-[var(--text-error)] text-small'>
70-
{benchmarks.error.message}
71-
</p>
69+
<div className='flex items-center justify-between gap-3'>
70+
<p role='alert' className='text-[var(--text-error)] text-small'>
71+
{benchmarks.error.message}
72+
</p>
73+
{!benchmarks.isFetchNextPageError && (
74+
<Chip disabled={benchmarks.isFetching} onClick={() => void benchmarks.refetch()}>
75+
Retry
76+
</Chip>
77+
)}
78+
</div>
7279
)}
7380
{benchmarks.isLoading ? (
7481
<p role='status' className='text-[var(--text-muted)] text-small'>

‎apps/sim/app/o/[organizationId]/benchmark/components/benchmark-results.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export function BenchmarkResults({
104104
{result.basis === 'missing'
105105
? ' · Missing from plan'
106106
: result.basis === 'reference'
107-
? ' · Reference resolved'
107+
? ' · Reference evidence'
108108
: ''}
109109
{review ? ` · ${result.correct ? 'Recovered' : 'Not recovered'}` : ''}
110110
</dt>

‎apps/sim/app/workspace/[workspaceId]/layout.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export default async function WorkspaceLayout({
8787
activeOrganizationId
8888
),
8989
isMothershipModelSelectorEnabled(),
90-
isPlanModeEnabled(session.user.id),
90+
isPlanModeEnabled(session.user.id).catch(() => false),
9191
resolveOrganizationEntryPath(session),
9292
isDashboardsEnabled(hostContext.hostOrganizationId),
9393
isWorkflowTestsEnabled(hostContext.hostOrganizationId),

‎apps/sim/lib/auth/auth.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ import { createSimAuthAdapter } from '@/lib/auth/sim-auth-adapter'
7474
import { admitSsoUser } from '@/lib/auth/sso/application/admit-sso-user'
7575
import { resolveSsoCallbackProviderId } from '@/lib/auth/sso/callback-provider'
7676
import { configuredSsoPlugin } from '@/lib/auth/sso/plugin'
77-
import { sendPlanWelcomeEmail } from '@/lib/billing'
7877
import {
7978
assertPersonalCheckoutAllowed,
8079
authorizeSubscriptionReference,
@@ -88,6 +87,7 @@ import {
8887
} from '@/lib/billing/checkout-admission'
8988
import {
9089
getOrganizationIdForSubscriptionReference,
90+
sendPlanWelcomeEmail,
9191
syncSubscriptionPlan,
9292
writeBillingInterval,
9393
} from '@/lib/billing/core/subscription'

0 commit comments

Comments
 (0)