Breeze RMM v0.104.0 — Quick Support sessions for agentless machines, two-person approval for high-stakes AI actions, third-party patch auto-approval on Update Rings, and a deep round of backup and connection-pool reliability work.
Summary
- Quick Support — one-time-code remote sessions on machines with no agent installed, via a small self-destructing client. (#3153)
- Two-person approval for AI actions — Tier-3 actions split into
supervised(self-approve in chat) andfour_eyes(a second admin must approve). (#3175) - Third-party patch auto-approval — a first-class Update Ring control with severity gating and a hold-after-release delay. (#3150)
- Backup: VSS snapshots actually work now — the COM vtable indices were wrong, so locked-file capture never functioned; verified end-to-end on real Windows hardware this cycle. (#3005, plus #3030, #3029, #3012, #3003)
- Connection-pool reliability — a watchdog that tells pool poisoning apart from a down database, held-connection warnings that name the caller, and worker DB contexts scoped to their statements. (#3224, #3221, #3222, #3220, #3235, #3024)
- Partner-wide script writes now require full partner org access — closing a privilege gap for selected-access partner users. (#3263)
Added
- Quick Support — generate a one-time code from
/remote; the end user downloads a self-destructing client from/quickthat redeems the code for a single session on an ephemeral device, then cleans itself up. (#3153) - Supervised / four-eyes approval scopes for Tier-3 AI actions — routine device work (run a script, restart a service) is self-approved by the requesting human with one click; financial issuance, tenant-shape changes, and M365/Google identity mutations require a second approver. Unclassified actions fail safe to
four_eyes. (#3175) - Third-party auto-approval on Update Rings — per-ring severity gating and a "hold after release" delay for third-party app updates, replacing the legacy rule category (existing rules are converted by migration, with counts logged). (#3150)
- VSS writer health on the device Backup tab — per-writer states and unprotected volumes, live from the last job. (#3030)
partialbackup status — runs with a disproportionate number of unreadable files land aspartialinstead of reporting success. (#3012)- Chunked software package uploads — byte-accurate progress and mid-flight cancel; large packages no longer ride a single request. (#3113)
- WAN/LAN IP columns on the devices list, sortable and persisted. (#2996)
- Procurement / order tracking on accepted quotes — mark lines ordered, partly received, received, or cancelled; export CSV. (#3111)
- Event-loop lag monitor — a stalled API main thread is reported as such (log + Prometheus gauges) instead of masquerading as a Postgres
CONNECT_TIMEOUT. On by default. (#3024) - Connection-pool health watchdog — distinguishes pool poisoning (an API restart fixes it) from the database being unreachable (it won't), exposed via
breeze_db_pool_health*metrics. On by default. (#3224) apple-app-site-associationis served, enabling iOS password-manager autofill for the mobile app. (#3171)
Improved
- Duplicate enrollment is idempotent — re-enrolling an existing hostname returns
201with a possible-replacement linkage persisted on the device (surfaced as a badge in the devices list), instead of failing with409. (#3063) - The whole patch catalog is pageable — more than 200 pending patches can now be viewed and bulk-approved. (#3195)
- SNMP polling backs off failing devices — the due-check runs off poll attempts with exponential backoff (5 min → 60 min cap), instead of re-polling an unreachable device every tick forever. (#3223)
- Stale-execution reaper honours each script's own
timeoutSeconds— long-running scripts are no longer failed at a flat 10 minutes, and short-timeout scripts no longer sit pending past their contract. (#3213) - Held-connection warnings name the opening caller (file:line), turning a needle-in-haystack hunt into a grep. (#3221)
/readyis evaluated per request instead of latching a boot-time snapshot, so a Redis/DB recovery is reflected without an API restart. (#2985)- Session expiry is handled cleanly — expiry notice on the login page, idle warning, and return-to-page after re-login. (#3086)
- Update Ring and config-policy UIs: features column with human-readable badges, timezone picker search, enrollment-key capacity display. (#2978, #2977, #2993, #3045)
Fixed
- Backup: VSS snapshot creation was completely broken — wrong
IVssBackupComponentsvtable indices meant locked-file capture never worked on any Windows device. Verified fixed on real hardware. (#3005) - Backup: system-state staging targeted a nonexistent snapshot path and degraded collection now surfaces a warning to the operator. (#3029, #3025)
- Backup: helper death is reported within seconds instead of waiting ~10 minutes on the stale reaper. (#3003)
- Dynamic device groups no longer blank the groups page — a phantom legacy rule stub plus an unguarded field read unmounted the whole island. (#3179, #3181)
- AI chat answers while an approval is pending — a pending Tier-3 intent no longer mutes the assistant. (#3104)
- Billing/catalog AI tools were unreachable from in-product chat — the Tier-2 read-only tool set is now contract-tested against the chat tool registry. (#3176)
- AI token accounting — cache-read/creation input tokens are counted, and partner-scope users no longer show zero usage. (#3180, #3103)
- Remote desktop start no longer self-collides — per-request IPC ids replace a shared constant. (#3194)
- The reaper no longer stamps "no response from agent" on commands the agent answered. (#3172)
- Automation script output is persisted for automation-triggered runs and shown in Run History. (#3186)
- UniFi integration — manual
type_sourcerespected on reconcile, and collector-agent pickers show real device names with site gating. (#3185, #3184) - PAM elevation approvers exclude disabled and invited users. (#3183)
- Enrollment-key "Hide expired" filter agrees with the status badge, and "Delete expired" spares keys with live installer tokens. (#3196, #2982)
- A malformed device id returns
404instead of500. (#2970) pnpm db:migrateactually applies migrations again (was a no-op wrapper). (#3070)
Security
- CVE-2026-59870 — js-yaml pinned to
>=4.3.1 <5.0.0(quadratic CPU in!!omapresolution). Upper-bounded deliberately to stay a patch bump. (#3212) - CVE-2026-67213 — nanoid pinned to
>=3.3.17 <4.0.0(infinite loop); reachable via the mobile app's runtime dependencies. (#3241) - CVE-2025-71329 / -71330 —
image-sizeDoS: no upstream fix exists. Sole dependent ismetro(React Native bundler) — build-time only, never in a shipped image. Accepted in.trivyignorewith justification and removal criteria. (#3241) - CVE-2026-18446 fast-uri → 3.1.5; CVE-2026-69152 brace-expansion → 5.0.9; RUSTSEC-2026-0221 event-listener → 5.4.2.
- Partner-wide scripts require the partner-wide capability — a partner user with
org_access = 'selected'could create, edit, or delete partner-wide scripts (which run as SYSTEM on every org under the partner, including orgs they hold no grant for). Create/edit/delete are now gated on full partner org access, matching every other partner-wide resource, and the web UI no longer offers the option to users who lack it. (#3263) - Sentry scrubs transaction events too, so a sampled trace can no longer ship a live API key. (#3081)
- Plaintext threat-signature strings are no longer embedded in shipped agent binaries (they tripped AV heuristics); verified absent by string-scanning the release binary. (#2973)
- Cloudflare Access logout redirect fails closed instead of trusting
Host. (#2976) redactLogFieldsno longer drops fields or reprototypes on__proto__. (#3164)FORCE_HTTPS/ proxy-trust misconfiguration is surfaced at the redirect itself. (#3067, #2988)
Self-hosters are encouraged to upgrade.
Known issues — backup (read if you rely on file backup)
Release QA verified the backup fixes on real Windows hardware, and found three problems that are not fixed in this release:
Update: all three are now fixed on
mainand ship with the next release. The fix for each is noted inline below; the workarounds still apply while you're on v0.104.0.
- Large file sets don't report completion (#3001) — a 4,000-file run completes on the agent (files are stored) but the result never reaches the server, so the job is eventually marked failed by the reaper. ~1,200 files is fine; the threshold is between. Fixed on
main(#3267) — the result is bounded to what the server accepts, and a rejected result is now logged loudly instead of vanishing. #3283 also raises the result payload cap from 1 MB to 5 MB (matching stdout/stderr), fixing restore browsing for backups with roughly more than 13,000 files. - Permission-denied files cost ~30 seconds each (#3259) — the fast-skip added in #3002 covers cloud-file placeholders but not plain NTFS
ERROR_ACCESS_DENIED. Fixed onmain(#3266) — plain ACL denials now take the fast-retry path too, so they cost milliseconds instead of ~30 seconds. - Enough denied files can destroy the whole backup (#3260) — the stalls can outlive the VSS shadow copy, after which every remaining healthy file fails too. In one reproduction, 15 denied files out of 40 produced a backup of zero files. Fixed on
main(#3266) — a lost VSS snapshot now aborts the backup immediately and loudly instead of silently producing a bad backup.
Until you're on the next release: keep backup selections tight, exclude folders you know are permission-restricted, and confirm jobs reach completed rather than trusting the absence of an alert.
Self-Hosting / Upgrade Notes
No breaking changes. Standard upgrade:
# bump BREEZE_VERSION in .env, then:
docker compose pull api web portal && docker compose up -d binaries-init api web portal(If you build from source, run pnpm install — dependency overrides changed.)
1. Check two env var values before you pull — a typo now refuses to boot
IP_ALLOWLIST_ENFORCEMENT_MODE and AGENT_AUTO_PROMOTE were declared but silently excluded from validation, so a typo (Off, disabled, falze) quietly fell back to a default. They are now validated (#2979) — the API refuses to start and names the variable. Valid values are unaffected:
grep -E '^(IP_ALLOWLIST_ENFORCEMENT_MODE|AGENT_AUTO_PROMOTE)=' .env
# IP_ALLOWLIST_ENFORCEMENT_MODE: enforce | off | (empty)
# AGENT_AUTO_PROMOTE: true | false2. Database — 19 migrations, no large-table rewrites
All idempotent, auto-applied on API boot via autoMigrate (unless AUTO_MIGRATE=false). No backfill touches a hot table; the one index on a large table is built CONCURRENTLY so it doesn't block writes. Five migrations write data and log row counts via RAISE WARNING (visible in your Postgres log):
| Migration | What it changes |
|---|---|
2026-08-08-drop-custom-alert-conditions
| Deletes/deactivates dead custom alert rules. The retired condition type never had an evaluator and could never fire; rules whose conditions were all custom are deleted (config-policy) or deactivated (alert_rules). Mixed rules untouched. If you built one, it disappears on upgrade.
|
2026-08-13-ring-third-party-auto-approve-backfill
| Converts legacy third-party patch rules to the new ring toggle |
2026-08-14-intent-approval-scope-and-deadlines
| Adds approval scope to AI action intents; pre-existing pending intents default to four_eyes (the stricter path)
|
2026-08-15-intent-release-lease-backfill
| Backfills execution leases on already-approved intents |
2026-08-15-snmp-poll-attempt-backoff
| Seeds last_poll_attempted_at from last_polled
|
3. No new required environment variables
Every new variable is optional with a built-in default, and all are mapped in the shipped compose files:
DB_POOL_HEALTH_*(six knobs, #3224) — pool-health watchdog, on by default.EVENT_LOOP_MONITOR_INTERVAL_MS/EVENT_LOOP_STARVATION_WARN_MS/EVENT_LOOP_MONITOR_DISABLED(#3024, mapped by #3238) — event-loop monitor, on by default. New boot log line[event-loop] Lag monitor started …and Prometheus gaugesbreeze_nodejs_eventloop_*on/metrics/scrape(alert onbreeze_nodejs_eventloop_monitored == 0).
4. Behavior changes
- Selected-access partner users can no longer create, edit, or delete partner-wide scripts (#3263). Full-access partner admins are unaffected. If a selected-access technician maintained partner-wide scripts before, a full-partner admin must take those over (or re-scope the scripts to specific orgs).
- Duplicate hostname enrollment returns
201instead of409(#3063), with the possible-replacement linkage persisted on the device row (not in the enroll response). - Pre-existing pending AI action intents become
four_eyesafter upgrade — they'll need a second approver or will expire. - Single-admin instances using four-eyes actions: approving your own
four_eyesaction requires a WebAuthn or hardware-key authenticator (assurance level 3) — password + TOTP is insufficient by design. Register a device under Settings → Profile first, or the approval returns403 step_up_required. - Quick Support on plain-HTTP self-hosted stacks: the downloaded client derives
https://<host>:<port>from its filename and cannot auto-connect over plain HTTP — pass--server http://…explicitly. (HTTPS stacks are unaffected.)
Full Changelog: v0.103.0...v0.104.0
What's Changed
- docs: document 0.103.0 rollout modes and new environment variables by @ToddHebebrand in #2971
- fix(security): bump event-listener to 5.4.2 for RUSTSEC-2026-0221 (unblocks Cargo Audit on all PRs) by @ToddHebebrand in #3009
- fix(alerts): remove the unimplemented
customalert condition type (#2948) by @ToddHebebrand in #2995 - feat(devices): optional WAN IP and LAN IP columns on the device list (#2503) by @ToddHebebrand in #2996
- fix(agent): report backup helper death instead of waiting for the stale reaper (#2998) by @ToddHebebrand in #3003
- fix(agent): bound the backup result payload to the IPC frame (#3001) by @ToddHebebrand in #3004
- fix(agent/backup): skip permanently-failing files without the 30s retry backoff (#2997) by @ToddHebebrand in #3002
- fix(migrations): move custom-alert-conditions cleanup out of the reserved 2026-08-06 block (red main) by @ToddHebebrand in #3015
- docs(claude): correct the stale Node pin in CLAUDE.md (22.20.0 -> 22.23.2) by @ToddHebebrand in #3017
- fix(agent/ipc): assign the IPC sequence number under the write lock (#3007) by @ToddHebebrand in #3013
- fix(backup): register snapshot id mid-run and add an orphan-snapshot reconcile path (#3006) by @ToddHebebrand in #3008
- fix(agent/backup): correct IVssBackupComponents vtable indices so VSS snapshots work (#2999) by @ToddHebebrand in #3005
- feat(backup): add a
partialterminal status for disproportionately failed runs (#3000) by @ToddHebebrand in #3012 - fix(api): stop agentWs command_result from double-holding pooled DB connections (#3021) by @ToddHebebrand in #3023
- test(agent/backup): cover the VSS shadow-path rewrite and stop its fallback failing silently by @ToddHebebrand in #3025
- fix(api): stop event-loop starvation masquerading as a Postgres CONNECT_TIMEOUT (#3022) by @ToddHebebrand in #3024
- fix(agent/backup): stop VSS from rewriting the system-state staging dir onto a path the snapshot never had (#3026) by @ToddHebebrand in #3029
- fix(ai-tools): validate MCP billing/contract/catalog payloads; fix void delete_draft return by @ToddHebebrand in #3028
- fix(api,web): show real installer capacity on Enrollment Keys (#2992) by @ToddHebebrand in #2993
- fix(backup): deliver VSS health to the server end to end (#3027) by @ToddHebebrand in #3030
- fix(ai-tools): repair 40 dead MCP tools; gate all Tier-3 actions behind interactive approval over MCP by @ToddHebebrand in #3031
- fix(api): exempt live installer bootstrap tokens from the on-demand enrollment-key purge (#2832) by @ToddHebebrand in #2982
- fix(api): surface the proxy-trust misconfiguration on the TRANSPORT-001 path (#2987) by @bdunncompany in #2988
- fix(backup): scope the backup-result job UPDATE to its device and re-source org from the job row (#3036) by @ToddHebebrand in #3040
- fix(api): short-link Windows installer downloads honor the installer-link TTL (#3038) by @ToddHebebrand in #3044
- fix(api,web): derive installer capacity and key status from live bootstrap tokens (#3039) by @ToddHebebrand in #3045
- fix(agent/backup): stop the IPC bounding tiers replacing job.Warning (#3035) by @ToddHebebrand in #3037
- fix(api): 404 instead of 500 on a malformed device id (#2968) by @bdunncompany in #2970
- docs: document the event-loop lag monitor + add a running next-release draft by @ToddHebebrand in #3069
- feat: enrollment idempotency — collisions detect instead of block, uninstall intent reaper, exactly-once bootstrap refund (#2764) by @ToddHebebrand in #3063
- fix(api): make pnpm db:migrate actually run autoMigrate() (#3065) by @ToddHebebrand in #3070
- fix(api): deterministic local unit test runs — proxy-header env stub, source-scan timeouts by @ToddHebebrand in #3079
- fix(devx): isolate concurrent worktree sessions from the shared test DB (#3066) by @ToddHebebrand in #3078
- fix(api): evaluate pending partners in the billing abuse detectors by @ToddHebebrand in #3082
- fix(deps): bump brace-expansion overrides to 5.0.9 for CVE-2026-69152 by @ToddHebebrand in #3083
- feat(billing): quote procurement breakdown + order tracking (to-be-ordered view, vendor snapshot, quote_orders) by @ToddHebebrand in #3111
- fix(ai): stop the chat turn from blocking on the slowest pending approval (#3089) by @ToddHebebrand in #3104
- fix(ai): give the vision/desktop tools at least the agent round-trip budget (#3091) by @bdunncompany in #3096
- fix(ai): approval-expiry tool error no longer claims the action will complete (#3090) by @ToddHebebrand in #3110
- fix(agent): apply event_logs_query XPath filter and fix blank timestamps (#3092) by @ToddHebebrand in #3101
- fix(ai): stop cutting execute_command JSON stdout at ~2KB; compact structurally with paging guidance (#3093) by @ToddHebebrand in #3102
- fix(ai): record SDK session usage for partner-scoped users + fallback token accumulation (#3095) by @ToddHebebrand in #3103
- fix(ai): tier read-only execute_command commandTypes down to auto-execute + audit (#3088) by @ToddHebebrand in #3105
- fix(ai): run device-bound chat tools under the device's org, not the login org (#3087) by @ToddHebebrand in #3106
- fix(deps): bump fast-uri override to 3.1.5 for CVE-2026-18446 by @ToddHebebrand in #3144
- fix(ai): record explicit tool_result when the SDK drops a call before dispatch by @ToddHebebrand in #3108
- chore(agents): issue-fixer runs on Opus, delegates to Sonnet subagents by @ToddHebebrand in #3152
- fix(software): chunked resumable package uploads with real progress (#2951) by @ToddHebebrand in #3113
- fix(mobile): dismiss approvals decided elsewhere, and the four TestFlight field defects by @ToddHebebrand in #3080
- feat(ai): auto-execute read-only Tier-2 calls under per_step approval (#3130) by @ToddHebebrand in #3156
- fix(security): scrub transaction events too, so a sampled trace cannot ship a live API key (#3077) by @bdunncompany in #3081
- chore(deps): bump werift from 0.24.1 to 0.24.2 in /e2e-tests by @dependabot[bot] in #3052
- chore(deps): bump playwright from 1.62.0 to 1.62.1 in /e2e-tests by @dependabot[bot] in #3053
- chore(deps): bump @types/office-js from 1.0.600 to 1.0.602 in the typescript-tooling group by @dependabot[bot] in #3054
- chore(deps): bump postcss from 8.5.24 to 8.5.25 in the tailwind group by @dependabot[bot] in #3056
- chore(deps): bump @aws-sdk/client-s3 from 3.1076.0 to 3.1100.0 by @dependabot[bot] in #3061
- chore(deps): bump node from
a0b9bf0tof70403ein /apps/api by @dependabot[bot] in #3071 - chore(deps): bump node from
a0b9bf0tof70403ein /apps/web by @dependabot[bot] in #3072 - chore(deps): bump node from
a0b9bf0tof70403ein /docker by @dependabot[bot] in #3073 - chore(deps): bump node from
a0b9bf0tof70403ein /apps/m365-graph-actions-executor by @dependabot[bot] in #3074 - chore(deps): bump node from
a0b9bf0tof70403ein /apps/m365-communications-executor by @dependabot[bot] in #3075 - chore(deps): bump node from
a0b9bf0tof70403ein /apps/m365-graph-read-executor by @dependabot[bot] in #3076 - chore(deps): bump the aws-sdk group in /agent with 5 updates by @dependabot[bot] in #3099
- chore(deps): bump google.golang.org/api from 0.290.0 to 0.291.0 in /agent by @dependabot[bot] in #3100
- chore(deps): bump the github-actions group with 4 updates by @dependabot[bot] in #3160
- fix(quotes): PDF layout rebalance, overlap fixes, and slimmer editor chrome by @ToddHebebrand in #3161
- fix(agent): stop desktop lease renewal from renewing after release by @ToddHebebrand in #3049
- feat(patching): third-party auto-approval as a first-class Update Ring control by @ToddHebebrand in #3150
- feat: Quick Support — one-time-code ad-hoc remote sessions (Milestone A) by @ToddHebebrand in #3153
- fix(agent): stop embedding plaintext threat-signature strings in shipped binaries (#2797) by @ToddHebebrand in #2973
- fix(auth): fail closed instead of trusting Host in cf-access-logout redirect (#2895) by @ToddHebebrand in #2976
- fix(api): validate every declared env var, not a hand-maintained subset (#2896) by @ToddHebebrand in #2979
- test(agent): make keepalive send-failure test deterministic (#2967) by @ToddHebebrand in #2981
- fix(api): evaluate /ready per request instead of latching a boot snapshot (#2974) by @ToddHebebrand in #2985
- fix(api): a behaviour common to legitimate operators should not alert alone by @ToddHebebrand in #3132
- fix(api,agent): size agent org rate limit by fleet, stop dropping patch inventory (#2728) by @ToddHebebrand in #2986
- fix(mobile): key mobile_devices on the per-install id so device blocking works (#2913) by @ToddHebebrand in #2983
- fix(web): populate config-policy Features column and label icon-only row actions (#2950) by @ToddHebebrand in #2978
- ci(security): retry osv-scanner download on transient network errors by @ToddHebebrand in #3169
- feat(web): session-expiry polish — atomic logout, expiry notices, proactive keepalive, idle warning by @ToddHebebrand in #3086
- fix(ai): register the five configuration-policy tools with the chat MCP server (#2814) by @ToddHebebrand in #2980
- feat(extensions): tenant-scoped extension installs (L1 install scoping) by @ToddHebebrand in #3032
- fix(agents): machine-readable codes on every rotation-confirm 409 (#2894) by @ToddHebebrand in #2984
- fix(web): searchable IANA timezone picker for site/org/partner settings (#2856) by @ToddHebebrand in #2977
- fix(ai-tools): validate MCP billing/contract/catalog payloads with shared Zod schemas; fix void delete_draft return by @ToddHebebrand in #3098
- fix(api): accept the terminal command_result shapes the agent actually sends (#3167) by @bdunncompany in #3170
- fix(api): make the Architecture dynamic-group filter match stored GOARCH (#3166) by @bdunncompany in #3168
- fix(agent,api): thread the caller's timeout budget through to the helper IPC (#3112) by @bdunncompany in #3165
- fix(api): stop redactLogFields dropping (and reprototyping on) proto (#3129) by @bdunncompany in #3164
- chore(deps): bump the react group across 1 directory with 3 updates by @dependabot[bot] in #3155
- chore(deps): bump @playwright/test from 1.62.0 to 1.62.1 in /e2e-tests by @dependabot[bot] in #3051
- chore(deps): bump posthog-react-native from 4.61.0 to 4.61.2 by @dependabot[bot] in #3058
- chore(deps): bump vite from 8.1.5 to 8.2.0 by @dependabot[bot] in #3062
- chore(deps): bump playwright from 1.62.0 to 1.62.1 by @dependabot[bot] in #3060
- chore(deps): bump babel-preset-expo from 57.0.3 to 57.0.5 by @dependabot[bot] in #3059
- chore(deps): bump hono from 4.12.32 to 4.12.33 in the hono group across 1 directory by @dependabot[bot] in #3057
- fix(api): warn at the FORCE_HTTPS redirect itself (TRANSPORT-001) (#3047) by @bdunncompany in #3067
- fix(ai): expose billing, quote, catalog and contract tools to the in-product chat by @ToddHebebrand in #3176
- fix(web): stop dynamic device groups blanking the /devices/groups page by @ToddHebebrand in #3179
- fix(api): materialize dynamic group membership instead of stranding it on a dead transaction by @ToddHebebrand in #3181
- fix(backup,ai): order backup jobs by started_at; count cached tokens as input by @ToddHebebrand in #3180
- fix(api): make the closed 2026-08-06 migration block fail at authoring time (#3016) by @ToddHebebrand in #3193
- fix(api): exclude disabled and invited users from PAM elevation approvers (#3174) by @bdunncompany in #3183
- fix(api): stop the reaper claiming "no response from agent" when the agent replied (#3097) by @bdunncompany in #3172
- fix(caddy): serve apple-app-site-association for iOS password autofill by @ToddHebebrand in #3171
- chore(deps): override js-yaml to 4.3.1 (CVE-2026-59870) — unblocks Trivy on all open PRs by @ToddHebebrand in #3212
- fix(api): make the enrollment-key "Hide expired" filter agree with the status badge (#3191) by @ToddHebebrand in #3196
- fix(web): page the whole patch catalog so >200 patches can be approved (#3157) by @ToddHebebrand in #3195
- fix(agent): give every start_desktop its own IPC id so it stops self-colliding (#3107) by @ToddHebebrand in #3194
- fix(api): persist stdout for automation-triggered script runs (#3162) by @ToddHebebrand in #3186
- fix(discovery): honour type_source='manual' in the UniFi reconcile path (#3011) by @ToddHebebrand in #3185
- fix(web): UniFi collector-agent pickers — real device names, drift + truncation surfaced (#3121) by @ToddHebebrand in #3184
- fix(api): honour the script's own timeoutSeconds in the stale reaper (#3190) by @bdunncompany in #3213
- fix(api): scope snmpWorker DB contexts to DB statements only (#3215) by @ToddHebebrand in #3220
- fix(db): name the opening caller in the #1105 hold warning (#3218) by @ToddHebebrand in #3221
- fix(api): scope alertWorker evaluate-all DB context to reads only (#3216) by @ToddHebebrand in #3222
- fix(api): diagnose and surface the postgres.js pool-poisoning failure (#3214) by @ToddHebebrand in #3224
- feat(ai): tier-3 supervised/four-eyes approval split — backend core by @ToddHebebrand in #3175
- fix(snmp): run the poll due-check off attempts and back off failures (#3217) by @ToddHebebrand in #3223
- feat(abuse): detect unbranded installer droppers on cloud object storage by @ToddHebebrand in #3232
- refactor(api): share the command-result handlers with the HTTP transport (#3097) by @bdunncompany in #3237
- chore(deps): override nanoid to 3.3.17; ignore unfixable image-size DoS CVEs by @ToddHebebrand in #3241
- fix(api): scope offlineDetector DB contexts to DB statements only (#3233) by @ToddHebebrand in #3235
- fix(compose): map the EVENT_LOOP_MONITOR_* knobs into the api service (#3236) by @bdunncompany in #3238
- fix(api): gate partner-wide script writes on canManagePartnerWidePolicies (#3262) by @bdunncompany in #3263
Full Changelog: v0.103.0...v0.104.0