Skip to content

Commit 7931397

Browse files
committed
fix(plan): address eligibility, benchmark state and desktop review findings
1 parent 3825a20 commit 7931397

54 files changed

Lines changed: 31188 additions & 971 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.

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ describe('native computer use transport', () => {
6262
expect(results).toEqual([JSON.parse(status), JSON.parse(status)])
6363
})
6464

65+
it('accepts a maximum-sized frame followed by another valid frame', async () => {
66+
const { client, reset } = helper(`let first; ${reader}
67+
if (!first) { first = request; return; }
68+
const a = JSON.stringify({id:first.id,result:${status}});
69+
const b = JSON.stringify({id:request.id,result:${status}});
70+
process.stdout.write(a.padEnd(16 * 1024 * 1024, ' ') + '\\n' + b + '\\n');
71+
});`)
72+
const results = await Promise.all([client.request('status', {}), client.request('status', {})])
73+
expect(results).toEqual([JSON.parse(status), JSON.parse(status)])
74+
expect(reset).not.toHaveBeenCalled()
75+
})
76+
6577
it('rejects malformed output and can start a fresh helper afterward', async () => {
6678
const { client, reset } = helper(`${reader}
6779
process.stdout.write(request.method==='bad'?'not json\\n':JSON.stringify({id:request.id,result:${status}})+'\\n');

‎apps/desktop/src/main/computer-use/native-client.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,12 @@ export class NativeComputerUseClient implements ComputerUseNativeClient {
6666
child.stdout.on('data', (chunk: Buffer) => {
6767
if (this.child !== child) return
6868
this.buffer = Buffer.concat([this.buffer, chunk])
69-
if (this.buffer.length > MAX_FRAME_BYTES) {
70-
this.stopWithError(new Error('Computer Use returned an oversized response.'))
71-
return
72-
}
7369
let newline = this.buffer.indexOf(10)
7470
while (newline >= 0) {
71+
if (newline > MAX_FRAME_BYTES) {
72+
this.stopWithError(new Error('Computer Use returned an oversized response.'))
73+
return
74+
}
7575
const line = this.buffer.subarray(0, newline).toString('utf8')
7676
this.buffer = this.buffer.subarray(newline + 1)
7777
try {
@@ -89,6 +89,8 @@ export class NativeComputerUseClient implements ComputerUseNativeClient {
8989
}
9090
newline = this.buffer.indexOf(10)
9191
}
92+
if (this.buffer.length > MAX_FRAME_BYTES)
93+
this.stopWithError(new Error('Computer Use returned an oversized response.'))
9294
})
9395
/** Native diagnostics must never copy app contents into application logs. */
9496
child.stderr.resume()

‎apps/sim/app/api/organizations/[id]/benchmarks/[benchmarkId]/route.ts‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ export const GET = defineInternalJsonRoute({
1717
contract: getBenchmarkContract,
1818
auth: internalSessionAuth,
1919
operation: benchmarkOperations.read,
20-
rateLimit: internalRateLimits.none({
21-
reason: 'The owner polls bounded benchmark artifacts under current source access',
22-
}),
20+
rateLimit: internalRateLimits.user({ bucketName: 'benchmark-read' }),
2321
errorPolicy: internalOrchestrationErrorPolicy,
2422
beforeParse: async ({ principal }) => {
2523
await requireBenchmarkOperator(principal)

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

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ interface BenchmarkEditorProps {
4040
busy: boolean
4141
saving: boolean
4242
stage: RunBenchmarkStageBody['stage'] | null
43-
onUpdate: (body: UpdateBenchmarkBody) => void
43+
onUpdate: (body: UpdateBenchmarkBody, onSaved: () => void) => void
4444
onRun: (stage: RunBenchmarkStageBody['stage'], runLabel?: string) => void
4545
}
4646

@@ -53,7 +53,16 @@ function BenchmarkEditor({
5353
onUpdate,
5454
onRun,
5555
}: BenchmarkEditorProps) {
56-
const [draft, setDraft] = useState(benchmark.artifacts)
56+
const [edit, setEdit] = useState<{
57+
version: number
58+
artifacts: BenchmarkCase['artifacts']
59+
} | null>(null)
60+
const draft = edit?.artifacts ?? benchmark.artifacts
61+
const changeDraft = (patch: Partial<BenchmarkCase['artifacts']>) =>
62+
setEdit((current) => ({
63+
version: current?.version ?? benchmark.version,
64+
artifacts: { ...(current?.artifacts ?? benchmark.artifacts), ...patch },
65+
}))
5766
const [runLabel, setRunLabel] = useState('')
5867
const { artifacts } = benchmark
5968
const referenceDirty =
@@ -76,31 +85,32 @@ function BenchmarkEditor({
7685

7786
return (
7887
<div className='flex flex-col gap-8 divide-y divide-[var(--border)] [&>section+section]:pt-8'>
88+
{edit && edit.version !== benchmark.version && (
89+
<div role='alert' className='flex items-center gap-3 text-[var(--text-muted)] text-small'>
90+
This benchmark changed on the server. Your edits are preserved; copy any text you need
91+
before loading the latest version.
92+
<Chip onClick={() => setEdit(null)}>Discard local edits</Chip>
93+
</div>
94+
)}
7995
<BenchmarkReference
8096
artifacts={draft}
8197
busy={busy}
8298
saving={saving}
8399
stage={stage}
84100
referenceDirty={referenceDirty}
85101
redactionDirty={redactionDirty}
86-
onChange={(patch) => setDraft((current) => ({ ...current, ...patch }))}
102+
onChange={changeDraft}
87103
onSave={() => {
88104
onUpdate(
89-
referenceDirty
90-
? {
91-
version: benchmark.version,
92-
...(draft.taskBrief !== artifacts.taskBrief
93-
? { taskBrief: draft.taskBrief }
94-
: {}),
95-
...(draft.referenceSpec !== artifacts.referenceSpec
96-
? { referenceSpec: draft.referenceSpec }
97-
: {}),
98-
}
99-
: {
100-
version: benchmark.version,
101-
redactedSpec: draft.redactedSpec,
102-
blanks: draft.blanks,
103-
}
105+
{
106+
version: edit?.version ?? benchmark.version,
107+
...(draft.taskBrief !== artifacts.taskBrief ? { taskBrief: draft.taskBrief } : {}),
108+
...(draft.referenceSpec !== artifacts.referenceSpec
109+
? { referenceSpec: draft.referenceSpec }
110+
: {}),
111+
...(redactionDirty ? { redactedSpec: draft.redactedSpec, blanks: draft.blanks } : {}),
112+
},
113+
() => setEdit((current) => (current === edit ? null : current))
104114
)
105115
}}
106116
onRun={onRun}
@@ -122,8 +132,8 @@ function BenchmarkEditor({
122132
>
123133
{!canPlan && (
124134
<p className='text-[var(--text-muted)] text-small'>
125-
Plan mode requires permission to create organization workspaces. An organization
126-
administrator can update the selected user’s access.
135+
The selected user needs Plan mode access and permission to create organization
136+
workspaces.
127137
</p>
128138
)}
129139
{artifacts.generatedSpec ? (
@@ -310,15 +320,15 @@ export function BenchmarkDetail({
310320
<BenchmarkHistory organizationId={organizationId} benchmarkId={benchmarkId} />
311321
) : (
312322
<BenchmarkEditor
313-
key={`${benchmark.id}:${benchmark.version}`}
323+
key={benchmark.id}
314324
benchmark={benchmark}
315325
canPlan={canPlan}
316326
busy={busy}
317327
saving={updateBenchmark.isPending}
318328
stage={stage}
319-
onUpdate={(body) => {
329+
onUpdate={(body, onSaved) => {
320330
runStage.reset()
321-
updateBenchmark.mutate(body)
331+
updateBenchmark.mutate(body, { onSuccess: onSaved })
322332
}}
323333
onRun={(nextStage, runLabel) => {
324334
updateBenchmark.reset()

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,10 @@ export function BenchmarkHistory({ organizationId, benchmarkId }: BenchmarkHisto
133133
Loading saved results…
134134
</p>
135135
)}
136-
{selected.data && (!baselineId || baseline.data) && (
136+
{selected.data && (
137137
<BenchmarkRunComparison
138138
run={selected.data.run}
139-
baseline={baseline.data?.run}
139+
baseline={baseline.error ? undefined : baseline.data?.run}
140140
organizationId={organizationId}
141141
/>
142142
)}

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

Lines changed: 20 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use client'
22

33
import { useRef, useState } from 'react'
4-
import { Chip, ChipInput, ChipTextarea, toast } from '@sim/emcn'
5-
import { Code, Plus, Trash, Upload } from '@sim/emcn/icons'
4+
import { Chip, ChipTextarea, toast } from '@sim/emcn'
5+
import { Code, Upload } from '@sim/emcn/icons'
66
import { getErrorMessage } from '@sim/utils/errors'
77
import type { BenchmarkCase, RunBenchmarkStageBody } from '@/lib/api/contracts/benchmarks'
88
import { BenchmarkJson } from '@/app/o/[organizationId]/benchmark/components/benchmark-json'
@@ -119,7 +119,10 @@ export function BenchmarkReference({
119119
<Chip
120120
leftIcon={Code}
121121
disabled={busy || referenceDirty || !referenceSpec.trim()}
122-
onClick={() => setJsonOpen(true)}
122+
onClick={() => {
123+
onChange({})
124+
setJsonOpen(true)
125+
}}
123126
>
124127
Edit JSON
125128
</Chip>
@@ -148,71 +151,28 @@ export function BenchmarkReference({
148151
<ChipTextarea
149152
aria-label='Redacted reference spec'
150153
value={redactedSpec}
151-
onChange={(event) => onChange({ redactedSpec: event.target.value })}
154+
readOnly
152155
rows={10}
153156
resizable
154157
/>
155158
<p className='text-[var(--text-muted)] text-small'>
156159
Each marker uses [[BLANK:id]], and its expected answer must restore the exact original
157160
passage. Keep discoverable details and remove answers revealed elsewhere in the text.
158161
</p>
159-
{blanks.map((blank, index) => (
160-
<div key={index} className='flex items-start gap-2'>
161-
<div className='grid min-w-0 flex-1 gap-2 sm:grid-cols-[140px_1fr]'>
162-
<ChipInput
163-
aria-label={`Blank ${index + 1} ID`}
164-
value={blank.id}
165-
maxLength={64}
166-
onChange={(event) =>
167-
onChange({
168-
redactedSpec: redactedSpec.replaceAll(
169-
`[[BLANK:${blank.id}]]`,
170-
`[[BLANK:${event.target.value}]]`
171-
),
172-
blanks: blanks.map((item, position) =>
173-
position === index ? { ...item, id: event.target.value } : item
174-
),
175-
})
176-
}
177-
/>
178-
<ChipTextarea
179-
aria-label={`Expected answer for blank ${index + 1}`}
180-
value={blank.answer}
181-
rows={2}
182-
resizable
183-
onChange={(event) =>
184-
onChange({
185-
blanks: blanks.map((item, position) =>
186-
position === index ? { ...item, answer: event.target.value } : item
187-
),
188-
})
189-
}
190-
/>
162+
<dl className='flex flex-col gap-3'>
163+
{blanks.map((blank) => (
164+
<div key={blank.id} className='flex flex-col gap-1'>
165+
<dt className='text-[var(--text-body)] text-small'>{blank.id}</dt>
166+
<dd className='whitespace-pre-wrap text-[var(--text-muted)] text-small'>
167+
{blank.answer}
168+
</dd>
191169
</div>
192-
<Chip
193-
leftIcon={Trash}
194-
aria-label={`Remove blank ${index + 1}`}
195-
onClick={() =>
196-
onChange({
197-
redactedSpec: redactedSpec.replaceAll(`[[BLANK:${blank.id}]]`, blank.answer),
198-
blanks: blanks.filter((_, position) => position !== index),
199-
})
200-
}
201-
/>
202-
</div>
203-
))}
204-
<div>
205-
<Chip
206-
leftIcon={Plus}
207-
onClick={() => {
208-
let suffix = blanks.length + 1
209-
while (blanks.some((blank) => blank.id === `detail_${suffix}`)) suffix += 1
210-
onChange({ blanks: [...blanks, { id: `detail_${suffix}`, answer: '' }] })
211-
}}
212-
>
213-
Add blank
214-
</Chip>
215-
</div>
170+
))}
171+
</dl>
172+
<p className='text-[var(--text-muted)] text-small'>
173+
Use Edit JSON to change markers and answers together. Changes are checked against the
174+
original reference before applying.
175+
</p>
216176
</fieldset>
217177
)}
218178
{dirty && (

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ export function BenchmarkResults({
4444
</span>
4545
)}
4646
{!result && answer?.evidenceError && (
47-
<div>
47+
<dl className='text-small'>
4848
<dt className='text-[var(--text-muted)]'>Evidence needs review</dt>
4949
<dd className='mt-1 whitespace-pre-wrap break-words text-[var(--text-body)]'>
5050
{answer.evidenceError}
5151
</dd>
52-
</div>
52+
</dl>
5353
)}
5454
</div>
5555
<dl className='flex flex-col gap-3 text-small'>

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,16 @@ export function BenchmarkStep({
2828
pending && 'text-[var(--text-muted)]'
2929
)}
3030
>
31-
{pending ? <Loader aria-label='Running' className='size-[14px] animate-spin' /> : number}
31+
{pending ? (
32+
<>
33+
<Loader animate className='size-[14px]' />
34+
<span role='status' className='sr-only'>
35+
Running {title}
36+
</span>
37+
</>
38+
) : (
39+
number
40+
)}
3241
</span>
3342
<div className='min-w-0 flex-1'>
3443
<h2 id={`benchmark-step-${number}`} className='text-[var(--text-primary)] text-base'>

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useState } from 'react'
44
import { Chip, ChipCombobox, ChipInput, ChipTextarea } from '@sim/emcn'
55
import { useBenchmarkWorkspaces, useCreateBenchmark } from '@/hooks/queries/benchmarks'
6+
import { useDebounce } from '@/hooks/use-debounce'
67

78
interface CreateBenchmarkProps {
89
organizationId: string
@@ -11,14 +12,26 @@ interface CreateBenchmarkProps {
1112
}
1213

1314
export function CreateBenchmark({ organizationId, runAsUserId, onCreated }: CreateBenchmarkProps) {
14-
const [sourceWorkspaceId, setSourceWorkspaceId] = useState('')
15+
const [sourceWorkspace, setSourceWorkspace] = useState<{ value: string; label: string } | null>(
16+
null
17+
)
18+
const [workspaceSearch, setWorkspaceSearch] = useState('')
1519
const [name, setName] = useState('')
1620
const [taskBrief, setTaskBrief] = useState('')
17-
const workspaces = useBenchmarkWorkspaces(organizationId, runAsUserId)
21+
const sourceWorkspaceId = sourceWorkspace?.value ?? ''
22+
const workspaces = useBenchmarkWorkspaces(
23+
organizationId,
24+
runAsUserId,
25+
useDebounce(workspaceSearch, 250)
26+
)
1827
const createBenchmark = useCreateBenchmark(organizationId)
19-
const options = (workspaces.data?.pages.flatMap((page) => page.workspaces) ?? []).map(
28+
const loadedOptions = (workspaces.data?.pages.flatMap((page) => page.workspaces) ?? []).map(
2029
(workspace) => ({ value: workspace.id, label: workspace.name })
2130
)
31+
const options =
32+
sourceWorkspace && !loadedOptions.some((option) => option.value === sourceWorkspace.value)
33+
? [sourceWorkspace, ...loadedOptions]
34+
: loadedOptions
2235

2336
return (
2437
<form
@@ -41,9 +54,12 @@ export function CreateBenchmark({ organizationId, runAsUserId, onCreated }: Crea
4154
aria-label='Source workspace'
4255
options={options}
4356
value={sourceWorkspaceId}
44-
onChange={setSourceWorkspaceId}
57+
onChange={(value) =>
58+
setSourceWorkspace(options.find((option) => option.value === value) ?? null)
59+
}
4560
placeholder='Select a workspace'
4661
searchable
62+
onSearchChange={setWorkspaceSearch}
4763
isLoading={workspaces.isLoading}
4864
error={workspaces.error?.message}
4965
disabled={createBenchmark.isPending}

‎apps/sim/app/o/[organizationId]/components/organization-sidebar/organization-sidebar.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export const OrganizationSidebar = memo(function OrganizationSidebar({
102102
organization.id,
103103
searchAccess.memberScoped,
104104
canUseHome,
105-
benchmarkEnabled && mothershipAvailable
105+
benchmarkEnabled
106106
)
107107
const settingsPath = organizationRoutes(organization.id).settings
108108
const isSettings = pathname === settingsPath || pathname?.startsWith(`${settingsPath}/`)

0 commit comments

Comments
 (0)