Skip to content

Commit c20cc1d

Browse files
authored
v0.8.17: ci improvements, outdated docs i18n removal, desktop improvements
2 parents e1d5978 + f516361 commit c20cc1d

1,557 files changed

Lines changed: 3722 additions & 304049 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-column-type/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ Registering the *type* is compiler-enforced. Registering its *metadata* is not,
149149
- [ ] `migrateCellsTo` / `migrateCellsFrom` added if the stored bytes change
150150
- [ ] New metadata keys added to `TYPE_SPECIFIC_COLUMN_KEYS` + `FOREIGN_METADATA_VERB`
151151
- [ ] Unit tests for `coerce` / `isCompatibleWith` round-trips, verified to fail without the code
152-
- [ ] Docs row added to `apps/docs/content/docs/en/tables/index.mdx`
152+
- [ ] Docs row added to `apps/docs/content/docs/tables/index.mdx`
153153

154154
## Final Validation (Required)
155155

.agents/skills/add-integration/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ bun run deployment-config:check
576576
bun run docs:check
577577
```
578578

579-
This creates `apps/docs/content/docs/en/integrations/{service}.mdx` — one page per service carrying the block's Actions and, if it has one, its Triggers section. Never hand-edit generated pages; the only editable region is the `{/* MANUAL-CONTENT */}` block (see `scripts/README.md`).
579+
This creates `apps/docs/content/docs/integrations/{service}.mdx` — one page per service carrying the block's Actions and, if it has one, its Triggers section. Never hand-edit generated pages; the only editable region is the `{/* MANUAL-CONTENT */}` block (see `scripts/README.md`).
580580

581581
The docs generator refreshes `packages/deployment-config/src/integrations.json`, and the deployment
582582
config generator projects service-account provider IDs from that catalog plus the canonical OAuth

.agents/skills/add-model/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ If the entry has `capabilities.thinking` or `capabilities.reasoningEffort`, it a
153153

154154
- **Anthropic-family (`anthropic`, `azure-anthropic`) thinking models MUST declare `capabilities.thinking.streamed`** (`'full' | 'summary' | 'none'`). Verify against Anthropic's current thinking-display and streaming docs: visible thinking returned by the API is summarized, including when Sim opts models whose default display is `omitted` into `display: 'summarized'` on agent-events runs, so current Claude thinking models use `'summary'`. Use `'full'` only if future official API docs explicitly guarantee raw thinking deltas. `bun run agent-stream-docs:check` (CI) fails if the field is missing.
155155
- Other families usually omit the field and inherit the provider default in `getThinkingStreamVisibility` (Gemini/OpenAI → summaries; Bedrock/Meta → none; OpenAI-compatible vendors with documented reasoning fields → full deltas). Set it explicitly only when the model deviates from its family.
156-
- After inserting the entry, run `bun run agent-stream-docs:generate` and commit the regenerated `apps/docs/content/docs/en/workflows/blocks/agent.mdx` — CI diffs it.
156+
- After inserting the entry, run `bun run agent-stream-docs:generate` and commit the regenerated `apps/docs/content/docs/workflows/blocks/agent.mdx` — CI diffs it.
157157
- Include the `streamed` value (with its source URL) in the verification report when set.
158158

159159
### Wrong family entirely?

.github/actions/docker-build/action.yml

Lines changed: 98 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ inputs:
2121
required: true
2222
max-cache-size-mb:
2323
description: >-
24-
Layer cache to retain after the post-job prune, in MB. Must stay above one
24+
Layer cache to retain after this action prunes, in MB. Must stay above one
2525
build's working set (base + dependency layers + RUN --mount=type=cache
2626
dirs) or every build evicts what the next one needs. Falls back to the
27-
small-image default below when empty.
27+
small-image default in the prune step when empty — the fallback lives there
28+
rather than here because callers pass this from a matrix field, and an unset
29+
matrix key arrives as the empty string, which counts as "provided" and would
30+
bypass an input `default:` entirely.
2831
required: false
2932

3033
# Registry logins must precede this action. provenance/sbom stay off: attestation
@@ -49,24 +52,16 @@ runs:
4952
PLATFORMS: ${{ inputs.platforms }}
5053
run: echo "value=${GITHUB_REPOSITORY##*/}/${FILE#./}/${PLATFORMS//\//-}" >> "$GITHUB_OUTPUT"
5154

52-
# max-cache-size-mb is what bounds the disk: BuildKit's default GC is
53-
# time-based only (layers unused for 8 days), and setup-docker-builder skips
54-
# pruning altogether when the value is empty. On a repo that builds this
55-
# often nothing ever ages out, so the disks grew without limit —
56-
# app.Dockerfile/linux-amd64 reached 351 GB inside a day, and realtime, whose
57-
# image is under 300 MB, sat at 249 GB. Sticky disks bill at ~$0.51/GB-month,
58-
# so that was real money for layers no build would ever read again.
59-
#
60-
# The fallback is here rather than an input `default:` because callers pass
61-
# this from a matrix field, and an unset matrix key arrives as the empty
62-
# string — which counts as "provided", so a `default:` would never apply and
63-
# a row that forgot the field would silently go back to unbounded growth.
55+
# This action does NOT bound the disk — see the prune step below. BuildKit's
56+
# own GC is time-based only (layers unused for 8 days), and these disks are
57+
# mounted many times a day, so nothing ever ages out: app.Dockerfile/linux-amd64
58+
# reached 351 GB inside a day of being created, and realtime, whose image is
59+
# under 300 MB, sat at 249 GB. Sticky disks bill at ~$0.51/GB-month.
6460
- name: Set up Blacksmith builder
6561
if: inputs.provider == '' || inputs.provider == 'blacksmith'
6662
uses: useblacksmith/setup-docker-builder@a5256a73e30f09e37e3eceb8ca36043d17621d24 # v2
6763
with:
6864
cache-key: ${{ steps.cache-key.outputs.value }}
69-
max-cache-size-mb: ${{ inputs.max-cache-size-mb || '25600' }}
7065

7166
- name: Build and push (Blacksmith)
7267
if: inputs.provider == '' || inputs.provider == 'blacksmith'
@@ -80,6 +75,94 @@ runs:
8075
provenance: false
8176
sbom: false
8277

78+
# Bound the layer cache ourselves. setup-docker-builder v1 took a
79+
# max-cache-size-mb input and pruned in its own post step, but the v2 rewrite
80+
# dropped it — and GitHub only WARNS on an unknown composite input, so passing
81+
# it to v2 silently did nothing for a day while the app disk sat at 200+ GB.
82+
#
83+
# This is v1's command verbatim (its dist/index.js pruneBuildkitCache), against
84+
# the fixed address v2 itself uses for `buildctl du` and `debug workers`:
85+
# sudo buildctl --addr tcp://127.0.0.1:1234 prune --all --keep-storage <MB>
86+
#
87+
# Note buildctl's --all is NOT `docker buildx prune --all`. Here it means
88+
# "include internal/frontend references" (cache/manager.go: without it, records
89+
# typed internal or frontend, and any ref shared with an external source, are
90+
# skipped). It does not wipe the cache, and --keep-storage still caps what is
91+
# retained -- it maps straight onto the modern MaxUsedSpace field, so it is the
92+
# buildctl spelling of --max-used-space rather than a deprecated alias.
93+
# `RUN --mount=type=cache` dirs are typed exec.cachemount and are reclaimed
94+
# either way; --all is here because it is what v1 used and it prunes strictly
95+
# more. Runs before the builder's post step, which is what commits the disk.
96+
#
97+
# Warn rather than fail: a cache that is too large is not worth failing a
98+
# deploy over. The du either side is what makes a silent no-op visible — the
99+
# failure mode that hid the v2 input regression in the first place.
100+
- name: Prune the layer cache
101+
if: (inputs.provider == '' || inputs.provider == 'blacksmith') && !cancelled()
102+
shell: bash
103+
env:
104+
KEEP_MB: ${{ inputs.max-cache-size-mb || '25600' }}
105+
run: |
106+
addr='tcp://127.0.0.1:1234'
107+
108+
# A zero or non-numeric value is NOT a no-op. buildctl parses
109+
# --keep-storage as a float, and BuildKit's cache manager treats
110+
# keepBytes==0 as "no cap" (`gcMode := opt.keepBytes != 0`), pruning
111+
# everything eligible rather than trimming to a limit. A typo such as
112+
# '40GB' — valid in turbo.json, but this flag is a bare MB number — would
113+
# silently empty the cache and make every later build cold, costing far
114+
# more than the storage it saves. Refuse instead.
115+
if ! [[ "$KEEP_MB" =~ ^[1-9][0-9]*$ ]]; then
116+
echo "::warning::max-cache-size-mb must be a positive whole number of MB, got '${KEEP_MB}' — skipping prune rather than risk wiping the cache"
117+
exit 0
118+
fi
119+
120+
# Print the whole Total line rather than picking a column: buildctl's du
121+
# table is whitespace-aligned and its layout is not a stable contract.
122+
#
123+
# The trailing `|| true` is load-bearing. Composite steps run under
124+
# `bash -e -o pipefail`, where `cur="$(total)"` takes the substitution's
125+
# exit status, so a failing du would abort the step and fail the build --
126+
# `echo "$(total)"` survives but the assignment in the settle loop does
127+
# not. buildctl exiting non-zero here is entirely plausible: deleting a
128+
# sticky disk out from under a running job makes buildkitd panic inside
129+
# DiskUsage, and grep also exits 1 whenever the table has no Total line.
130+
# Cache hygiene must never be able to fail a deploy.
131+
total() { sudo buildctl --addr "$addr" du 2>/dev/null | grep -iE '^total:' | tr -s ' \t' ' ' || true; }
132+
echo "before prune -> $(total)"
133+
134+
if sudo buildctl --addr "$addr" prune --all --keep-storage "$KEEP_MB"; then
135+
# buildctl prune returns BEFORE buildkitd has finished deleting
136+
# (moby/buildkit#1198). The builder's post step then SIGTERMs buildkitd
137+
# and SIGKILLs it after 30s (shutdownBuildkitd: `const a=3e4`); on
138+
# SIGKILL it sets sigkillUsed and SKIPS the sticky disk commit, throwing
139+
# away this run's cache and risking a corrupt bbolt metadata DB. So wait
140+
# for du to stop moving before handing back. Bounded — this is hygiene,
141+
# not correctness, and the steady-state trim settles almost at once.
142+
prev=''; stable=0
143+
for _ in $(seq 1 60); do
144+
cur="$(total)"
145+
# An empty reading means du FAILED, never that the cache is empty:
146+
# buildctl prints its `Total:` line unconditionally (cmd/buildctl
147+
# diskusage.go), so an empty cache still reports `Total: 0B`. Without
148+
# the -n guard the initial prev='' matched two empty readings and the
149+
# loop exited after ~2s -- precisely when du is failing and the prune
150+
# is most likely still deleting. Treat it as unstable and wait out the
151+
# bound instead.
152+
if [ -n "$cur" ] && [ "$cur" = "$prev" ]; then
153+
stable=$((stable + 1))
154+
[ "$stable" -ge 2 ] && break
155+
else
156+
stable=0
157+
fi
158+
prev="$cur"
159+
sleep 2
160+
done
161+
echo "after prune -> $(total) (keep-storage ${KEEP_MB} MB)"
162+
else
163+
echo "::warning::Layer cache prune failed; this sticky disk is unbounded for this run"
164+
fi
165+
83166
- name: Set up Docker Buildx
84167
if: inputs.provider != '' && inputs.provider != 'blacksmith'
85168
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,7 @@ jobs:
630630
with:
631631
filters: |
632632
docs:
633-
- 'apps/docs/content/docs/en/**'
633+
- 'apps/docs/content/docs/**'
634634
- 'apps/sim/scripts/process-docs.ts'
635635
- 'apps/sim/lib/chunkers/**'
636636

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ start-collector.sh
8686

8787
## Helm Chart Tests
8888
helm/sim/test
89-
i18n.cache
9089

9190
## Claude Code
9291
.claude/launch.json

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

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,8 @@ describe('browser-agent screenshot capture', () => {
548548
expect(shot).toEqual({
549549
dataUrl: `data:image/jpeg;base64,${Buffer.from('resized').toString('base64')}`,
550550
scale: 0.5,
551+
viewport: { width: 2048, height: 1024 },
552+
imageSize: { width: 1024, height: 512 },
551553
})
552554
})
553555

@@ -558,14 +560,115 @@ describe('browser-agent screenshot capture', () => {
558560

559561
const image = vi.mocked(nativeImage.createFromBuffer).mock.results[0].value
560562
expect(image.resize).not.toHaveBeenCalled()
561-
expect(shot).toEqual({ dataUrl: 'data:image/jpeg;base64,c2lt', scale: 0.5 })
563+
expect(shot).toEqual({
564+
dataUrl: 'data:image/jpeg;base64,c2lt',
565+
scale: 0.5,
566+
viewport: { width: 2048, height: 1024 },
567+
imageSize: { width: 1024, height: 512 },
568+
})
562569
})
563570

564571
it('returns the raw capture when the image cannot be decoded', async () => {
565572
const { contents } = captureFixture(null)
566573

567574
const shot = await captureScreenshot(contents)
568575

569-
expect(shot).toEqual({ dataUrl: 'data:image/jpeg;base64,c2lt', scale: 0.5 })
576+
expect(shot).toEqual({
577+
dataUrl: 'data:image/jpeg;base64,c2lt',
578+
scale: 0.5,
579+
viewport: { width: 2048, height: 1024 },
580+
imageSize: null,
581+
})
570582
})
583+
584+
it('does not expose deprecated device-pixel metrics as a CSS viewport', async () => {
585+
const { contents } = captureFixture({ width: 1024, height: 512 })
586+
vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => {
587+
if (method === 'Page.getLayoutMetrics') {
588+
return Promise.resolve({ layoutViewport: { clientWidth: 2048, clientHeight: 1024 } })
589+
}
590+
if (method === 'Page.captureScreenshot') return Promise.resolve({ data: 'c2lt' })
591+
return Promise.resolve(undefined)
592+
})
593+
594+
const shot = await captureScreenshot(contents)
595+
596+
expect(shot.viewport).toBeNull()
597+
expect(shot.imageSize).toEqual({ width: 1024, height: 512 })
598+
})
599+
600+
it('accepts stable finite scroll offsets around the capture', async () => {
601+
const { contents } = captureFixture({ width: 1024, height: 512 })
602+
vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => {
603+
if (method === 'Page.getLayoutMetrics') {
604+
return Promise.resolve({
605+
cssLayoutViewport: {
606+
clientWidth: 2048,
607+
clientHeight: 1024,
608+
pageX: 12,
609+
pageY: 34,
610+
},
611+
})
612+
}
613+
if (method === 'Page.captureScreenshot') return Promise.resolve({ data: 'c2lt' })
614+
return Promise.resolve(undefined)
615+
})
616+
617+
await expect(captureScreenshot(contents)).resolves.toMatchObject({
618+
viewport: { width: 2048, height: 1024 },
619+
imageSize: { width: 1024, height: 512 },
620+
})
621+
})
622+
623+
it.each([
624+
[
625+
'dimensions',
626+
{ cssLayoutViewport: { clientWidth: 2048, clientHeight: 1024 } },
627+
{ cssLayoutViewport: { clientWidth: 1024, clientHeight: 512 } },
628+
],
629+
[
630+
'metric units',
631+
{ cssLayoutViewport: { clientWidth: 2048, clientHeight: 1024 } },
632+
{ layoutViewport: { clientWidth: 2048, clientHeight: 1024 } },
633+
],
634+
[
635+
'horizontal scroll offset',
636+
{ cssLayoutViewport: { clientWidth: 2048, clientHeight: 1024, pageX: 0, pageY: 20 } },
637+
{ cssLayoutViewport: { clientWidth: 2048, clientHeight: 1024, pageX: 10, pageY: 20 } },
638+
],
639+
[
640+
'vertical scroll offset',
641+
{ cssLayoutViewport: { clientWidth: 2048, clientHeight: 1024, pageX: 10, pageY: 20 } },
642+
{ cssLayoutViewport: { clientWidth: 2048, clientHeight: 1024, pageX: 10, pageY: 30 } },
643+
],
644+
[
645+
'offset validity',
646+
{ cssLayoutViewport: { clientWidth: 2048, clientHeight: 1024, pageX: 0, pageY: 0 } },
647+
{
648+
cssLayoutViewport: {
649+
clientWidth: 2048,
650+
clientHeight: 1024,
651+
pageX: 0,
652+
pageY: Number.NaN,
653+
},
654+
},
655+
],
656+
['availability', {}, {}],
657+
])(
658+
'rejects a capture when viewport %s change during CDP capture',
659+
async (_label, before, after) => {
660+
const { contents } = captureFixture({ width: 1024, height: 512 })
661+
let metricsRead = 0
662+
vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => {
663+
if (method === 'Page.getLayoutMetrics') {
664+
metricsRead++
665+
return Promise.resolve(metricsRead === 1 ? before : after)
666+
}
667+
if (method === 'Page.captureScreenshot') return Promise.resolve({ data: 'c2lt' })
668+
return Promise.resolve(undefined)
669+
})
670+
671+
await expect(captureScreenshot(contents)).rejects.toThrow(/viewport changed/)
672+
}
673+
)
571674
})

0 commit comments

Comments
 (0)