Skip to content

Commit 4b358b6

Browse files
committed
sandbox export keeps the code's result; --trigger implies --manual; deps mock skeleton; run tools lift the terminal output and take select; lint all-clear names what it checked; docs chunker keeps code spans
1 parent b0f53a4 commit 4b358b6

18 files changed

Lines changed: 585 additions & 122 deletions

File tree

apps/docs/content/docs/cli/reference.mdx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3096,7 +3096,7 @@ sim tables groups create <tableId> [options]
30963096
| Option | Required | Description |
30973097
| --- | --- | --- |
30983098
| `--group <json\|@file>` | Yes | Workflow or enrichment producer definition. (JSON, or @path / @- to read a file or stdin). |
3099-
| `--output-columns <json\|@file>` | Yes | Columns created for producer outputs. (JSON, or @path / @- to read a file or stdin). |
3099+
| `--output-columns <json\|@file>` | No | Columns to create for producer outputs. An entry naming a column the table already has attaches that column to the group instead of creating it (its `type` must match), and an output whose column already exists may omit its entry entirely — so `[]` attaches existing columns only. (JSON, or @path / @- to read a file or stdin). |
31003100
| `--auto-run` | No | Whether to schedule existing rows after group creation. |
31013101
| `--no-auto-run` | No | Send --auto-run as false. |
31023102

@@ -3638,7 +3638,7 @@ sim tables dispatches get <tableId> <dispatchId>
36383638

36393639
### sim tables dispatches list
36403640

3641-
List Active Run Dispatches
3641+
List Run Dispatches
36423642

36433643
```bash
36443644
sim tables dispatches list <tableId>
@@ -5157,14 +5157,14 @@ sim workflows run <workflowId> [options]
51575157
| `--input <json\|@file>` | No | Trigger input as JSON (JSON, or @path / @- to read a file or stdin). |
51585158
| `--async` | No | Queue the run and return immediately. |
51595159
| `--execution-timeout-seconds <value>` | No | Requested server-side timeout for an asynchronous run, in seconds. An upper bound, not the effective timeout: the run uses the smaller of this value and the plan's execution timeout, so requesting more than the plan allows silently yields the plan timeout. Rejected with `400` unless `async` is true. |
5160-
| `--select-output <value...>` | No | Return blockName.field values (e.g. agent_1.content), or childWorkflowId.blockName.field for a child workflow (applies to every invocation) — in blockOutputs on a sync run, or from the streamed result with --follow; missing fields are omitted. Not available with --async (space-separated, or @path / @- with one value per line; @@value for a literal leading @). |
5160+
| `--select-output <value...>` | No | Return blockName.path values (e.g. agent_1.content), or childWorkflowId.blockName.path for a child workflow (applies to every invocation) — in blockOutputs on a sync run, or from the streamed result with --follow; missing paths are omitted. Not available with --async (space-separated, or @path / @- with one value per line; @@value for a literal leading @). |
51615161
| `--include-file-base64` | No | Inline eligible output files as base64 content. Rejected when `async` is true. |
51625162
| `--no-include-file-base64` | No | Send --include-file-base64 as false. |
51635163
| `--base64-max-bytes <value>` | No | Maximum total bytes of file content to inline as base64, lowering but never raising the server limit of 16 MiB. Rejected when `async` is true. |
51645164
| `--run-id <value>` | No | One-shot identifier for this run; NOT an idempotency key — reusing a claimed value fails with RUN_ID_CONFLICT instead of replaying the first result, and a fresh value starts another run. |
51655165
| `--manual` | No | Run the current saved workflow state instead of the active deployment. |
5166-
| `--trigger <blockId>` | No | Enter a manual run through this runnable trigger (requires --manual). |
5167-
| `--mock-payload` | No | Use the selected trigger's server-derived mock payload (requires --manual). |
5166+
| `--trigger <blockId>` | No | Enter the run through this runnable trigger; runs the current saved workflow state (implies --manual). |
5167+
| `--mock-payload` | No | Use the selected trigger's server-derived mock payload; runs the current saved workflow state (implies --manual). |
51685168
| `--from-block <blockId>` | No | Run manually from this saved workflow block. |
51695169
| `--source-run <runId>` | No | Prior run whose persisted state supplies upstream outputs (requires --from-block). |
51705170
| `--follow` | No | Stream the run as it happens; progress on stderr, result on stdout. The stream reports only success and output, so the result omits the run id and timings a non-streaming run returns. |

apps/docs/content/docs/cli/tables.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ sim tables groups create <tableId> [options]
112112
| Option | Required | Description |
113113
| --- | --- | --- |
114114
| `--group <json\|@file>` | Yes | Workflow or enrichment producer definition. (JSON, or @path / @- to read a file or stdin). |
115-
| `--output-columns <json\|@file>` | Yes | Columns created for producer outputs. (JSON, or @path / @- to read a file or stdin). |
115+
| `--output-columns <json\|@file>` | No | Columns to create for producer outputs. An entry naming a column the table already has attaches that column to the group instead of creating it (its `type` must match), and an output whose column already exists may omit its entry entirely — so `[]` attaches existing columns only. (JSON, or @path / @- to read a file or stdin). |
116116
| `--auto-run` | No | Whether to schedule existing rows after group creation. |
117117
| `--no-auto-run` | No | Send --auto-run as false. |
118118

@@ -614,7 +614,7 @@ sim tables dispatches get <tableId> <dispatchId>
614614

615615
</CommandTable>
616616

617-
## List active run dispatches
617+
## List run dispatches
618618

619619
```bash
620620
sim tables dispatches list <tableId>

apps/docs/content/docs/cli/workflows.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -532,14 +532,14 @@ sim workflows run <workflowId> [options]
532532
| `--input <json\|@file>` | No | Trigger input as JSON (JSON, or @path / @- to read a file or stdin). |
533533
| `--async` | No | Queue the run and return immediately. |
534534
| `--execution-timeout-seconds <value>` | No | Requested server-side timeout for an asynchronous run, in seconds. An upper bound, not the effective timeout: the run uses the smaller of this value and the plan's execution timeout, so requesting more than the plan allows silently yields the plan timeout. Rejected with `400` unless `async` is true. |
535-
| `--select-output <value...>` | No | Return blockName.field values (e.g. agent_1.content), or childWorkflowId.blockName.field for a child workflow (applies to every invocation) — in blockOutputs on a sync run, or from the streamed result with --follow; missing fields are omitted. Not available with --async (space-separated, or @path / @- with one value per line; @@value for a literal leading @). |
535+
| `--select-output <value...>` | No | Return blockName.path values (e.g. agent_1.content), or childWorkflowId.blockName.path for a child workflow (applies to every invocation) — in blockOutputs on a sync run, or from the streamed result with --follow; missing paths are omitted. Not available with --async (space-separated, or @path / @- with one value per line; @@value for a literal leading @). |
536536
| `--include-file-base64` | No | Inline eligible output files as base64 content. Rejected when `async` is true. |
537537
| `--no-include-file-base64` | No | Send --include-file-base64 as false. |
538538
| `--base64-max-bytes <value>` | No | Maximum total bytes of file content to inline as base64, lowering but never raising the server limit of 16 MiB. Rejected when `async` is true. |
539539
| `--run-id <value>` | No | One-shot identifier for this run; NOT an idempotency key — reusing a claimed value fails with RUN_ID_CONFLICT instead of replaying the first result, and a fresh value starts another run. |
540540
| `--manual` | No | Run the current saved workflow state instead of the active deployment. |
541-
| `--trigger <blockId>` | No | Enter a manual run through this runnable trigger (requires --manual). |
542-
| `--mock-payload` | No | Use the selected trigger's server-derived mock payload (requires --manual). |
541+
| `--trigger <blockId>` | No | Enter the run through this runnable trigger; runs the current saved workflow state (implies --manual). |
542+
| `--mock-payload` | No | Use the selected trigger's server-derived mock payload; runs the current saved workflow state (implies --manual). |
543543
| `--from-block <blockId>` | No | Run manually from this saved workflow block. |
544544
| `--source-run <runId>` | No | Prior run whose persisted state supplies upstream outputs (requires --from-block). |
545545
| `--follow` | No | Stream the run as it happens; progress on stderr, result on stdout. The stream reports only success and output, so the result omits the run id and timings a non-streaming run returns. |

apps/sim/lib/chunkers/docs-chunker.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,23 @@ describe('cleanContent FAQ extraction', () => {
110110
})
111111
})
112112

113+
describe('cleanContent keeps code intact', () => {
114+
it('leaves reference tokens inside fenced and inline code alone while stripping prose tags', () => {
115+
const chunker = new DocsChunker({ chunkSize: 500 })
116+
const cleaned = (chunker as unknown as { cleanContent: (c: string) => string }).cleanContent(
117+
[
118+
'Use <Callout>this</Callout> block. Reference `<start.input>` in code:',
119+
'```javascript',
120+
'return <start.input>.toLowerCase().includes({{ENV}})',
121+
'```',
122+
].join('\n')
123+
)
124+
expect(cleaned).not.toContain('<Callout>')
125+
expect(cleaned).toContain('`<start.input>`')
126+
expect(cleaned).toContain('return <start.input>.toLowerCase().includes({{ENV}})')
127+
})
128+
})
129+
113130
describe('cleanContent scaffolding strips', () => {
114131
it('still strips imports, exports, comments, and code-ish brace expressions', () => {
115132
const cleaned = cleanContent(

apps/sim/lib/chunkers/docs-chunker.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,18 +252,32 @@ export class DocsChunker {
252252
return { chunks: finalChunks, cleanedContent }
253253
}
254254

255+
/**
256+
* Strips MDX scaffolding from prose while leaving code untouched: a fenced block or an
257+
* inline span is where `<start.input>` references and `{{SECRET}}` tokens live, and the
258+
* tag and brace strips below would otherwise erase exactly the part of a code sample
259+
* that shows how a reference is written.
260+
*/
255261
private cleanContent(content: string): string {
256-
return content
262+
const normalized = content
257263
.replace(/\r\n/g, '\n')
258264
.replace(/\r/g, '\n')
259265
.replace(/^import\s+.*$/gm, '')
260266
.replace(/^export\s+.*$/gm, '')
261267
.replace(/<FAQ\s+items=\{\[([\s\S]*?)\]\}\s*\/>/g, (_m, items: string) =>
262268
extractFaqProse(items)
263269
)
264-
.replace(/<\/?[a-zA-Z][^>]*>/g, ' ')
265-
.replace(/\{\/\*[\s\S]*?\*\/\}/g, ' ')
266-
.replace(/\{[^{}]*\}/g, ' ')
270+
const segments = normalized.split(/(```[\s\S]*?```|`[^`\n]+`)/g)
271+
return segments
272+
.map((segment, index) =>
273+
index % 2 === 1
274+
? segment
275+
: segment
276+
.replace(/<\/?[a-zA-Z][^>]*>/g, ' ')
277+
.replace(/\{\/\*[\s\S]*?\*\/\}/g, ' ')
278+
.replace(/\{[^{}]*\}/g, ' ')
279+
)
280+
.join('')
267281
.replace(/\n{3,}/g, '\n\n')
268282
.replace(/[ \t]{2,}/g, ' ')
269283
.trim()

apps/sim/lib/function-execution/execute-request.test.ts

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -921,14 +921,64 @@ describe('Function execution request', () => {
921921
}),
922922
})
923923
)
924-
expect(data.output.result.files).toHaveLength(2)
924+
expect(data.output.result).toBe('done')
925+
expect(data.output.exported.files).toHaveLength(2)
926+
expect(data.output.message).toContain('Exported 2 sandbox files')
927+
expect(data.output.exported.message).toBe(data.output.message)
925928
expect(data.output.cost).toEqual({ input: 0, output: 0, total: 0.00023456 })
926929
expect(data.resources).toEqual([
927930
expect.objectContaining({ path: 'files/reports/chart.png' }),
928931
expect.objectContaining({ path: 'files/reports/summary.json' }),
929932
])
930933
})
931934

935+
it("keeps the code's returned rows beside the sandbox export receipt", async () => {
936+
envFlagsMock.isRemoteSandboxEnabled = true
937+
const rows = [{ name: 'Ada' }, { name: 'Grace' }]
938+
mockExecuteInSandbox.mockResolvedValueOnce({
939+
result: rows,
940+
stdout: 'ok',
941+
sandboxId: 'sandbox-123',
942+
exportedFiles: { '/home/user/report.txt': 'name\nAda\nGrace\n' },
943+
})
944+
945+
const response = await POST(
946+
createMockRequest('POST', {
947+
code: '__sim_result__ = [{"name": "Ada"}, {"name": "Grace"}]',
948+
language: 'python',
949+
workspaceId: 'workspace-1',
950+
outputs: {
951+
files: [
952+
{
953+
path: 'files/report.txt',
954+
sandboxPath: '/home/user/report.txt',
955+
mimeType: 'text/plain',
956+
},
957+
],
958+
},
959+
})
960+
)
961+
const data = await response.json()
962+
963+
expect(response.status).toBe(200)
964+
expect(data.success).toBe(true)
965+
// The table writer and the returned-value file writer both read
966+
// `output.result`, so the export receipt must not displace the rows.
967+
expect(data.output.result).toEqual(rows)
968+
expect(data.output.exported).toEqual({
969+
message: expect.stringContaining('Sandbox file exported to files/report.txt'),
970+
files: [
971+
expect.objectContaining({
972+
fileId: 'wf_report_txt',
973+
vfsPath: 'files/report.txt',
974+
sandboxPath: '/home/user/report.txt',
975+
}),
976+
],
977+
})
978+
expect(data.output.message).toBe(data.output.exported.message)
979+
expect(data.resources).toEqual([expect.objectContaining({ path: 'files/report.txt' })])
980+
})
981+
932982
it('atomically classifies text exports and acknowledges the durable v2 capability', async () => {
933983
envFlagsMock.isRemoteSandboxEnabled = true
934984
mockExecuteInSandbox.mockResolvedValueOnce({
@@ -1221,9 +1271,9 @@ describe('Function execution request', () => {
12211271
)
12221272

12231273
expect(response.status).toBe(200)
1224-
expect((await response.json()).output.result).toEqual(
1225-
expect.objectContaining({ fileId: 'wf_output_txt', vfsPath: 'files/output.txt' })
1226-
)
1274+
expect((await response.json()).output.exported.files).toEqual([
1275+
expect.objectContaining({ fileId: 'wf_output_txt', vfsPath: 'files/output.txt' }),
1276+
])
12271277
expect(mockExecuteInSandbox).toHaveBeenCalledOnce()
12281278
expect(mockWriteWorkspaceFileByPath).toHaveBeenCalledWith(
12291279
expect.objectContaining({
@@ -2005,9 +2055,10 @@ describe('Function execution request', () => {
20052055
expect(response.status).toBe(200)
20062056
expect(data.success).toBe(true)
20072057
expect(mockWriteWorkspaceFileByPath).toHaveBeenCalledTimes(1)
2008-
expect(data.output.result.unchanged).toBe(true)
2009-
expect(data.output.result.message).toContain('byte-identical to the previous version')
2010-
expect(data.output.result.message).toContain('/home/user/doc.md')
2058+
expect(data.output.result).toBe('done')
2059+
expect(data.output.exported.files[0].unchanged).toBe(true)
2060+
expect(data.output.message).toContain('byte-identical to the previous version')
2061+
expect(data.output.message).toContain('/home/user/doc.md')
20112062
})
20122063

20132064
it('continues an overwrite when the advisory comparison fails', async () => {
@@ -2045,8 +2096,8 @@ describe('Function execution request', () => {
20452096
expect(response.status).toBe(200)
20462097
expect(data.success).toBe(true)
20472098
expect(mockWriteWorkspaceFileByPath).toHaveBeenCalledTimes(1)
2048-
expect(data.output.result).toMatchObject({ unchanged: false })
2049-
expect(data.output.result).not.toHaveProperty('previousSize')
2099+
expect(data.output.exported.files[0]).toMatchObject({ unchanged: false })
2100+
expect(data.output.exported.files[0]).not.toHaveProperty('previousSize')
20502101
})
20512102

20522103
it('reports size, previousSize, and sha256 receipts on a successful overwrite export', async () => {
@@ -2088,12 +2139,13 @@ describe('Function execution request', () => {
20882139
expect(data.success).toBe(true)
20892140
// Sizes differ, so the current content is never downloaded for comparison.
20902141
expect(mockFetchWorkspaceFileBuffer).not.toHaveBeenCalled()
2091-
expect(data.output.result.size).toBe(Buffer.byteLength(newContent, 'utf-8'))
2092-
expect(data.output.result.previousSize).toBe(36728)
2093-
expect(data.output.result.sha256).toMatch(/^[0-9a-f]{64}$/)
2094-
expect(data.output.result.unchanged).toBe(false)
2095-
expect(data.output.result.message).toContain('replaced 36728 bytes')
2096-
expect(data.output.result.message).toContain('sha256:')
2142+
const [exportedFile] = data.output.exported.files
2143+
expect(exportedFile.size).toBe(Buffer.byteLength(newContent, 'utf-8'))
2144+
expect(exportedFile.previousSize).toBe(36728)
2145+
expect(exportedFile.sha256).toMatch(/^[0-9a-f]{64}$/)
2146+
expect(exportedFile.unchanged).toBe(false)
2147+
expect(data.output.message).toContain('replaced 36728 bytes')
2148+
expect(data.output.message).toContain('sha256:')
20972149
// The python wrapper prints the marker with a leading \n so it always
20982150
// starts a fresh line even after non-newline-terminated user output.
20992151
const e2bCode = mockExecuteInSandbox.mock.calls[0][0].code as string

0 commit comments

Comments
 (0)