Keep release files at the archive root and reject product-named directory components before CI packages artifacts.
Use neutral directories for media staging, WebUI uploads, stream storage, and MCP downloads so packaged and runtime layouts remain consistent.
Phase 2 of the debug-console expansion. Two backend endpoints under the
existing /api/* bearer auth:
- POST /api/debug/invoke-stream — drive a Stream API action (#163) and
relay every frame to the browser over an SSE body. The frontend reads
it with fetch()+a stream reader (header auth), so it needs no SSE
query-token allowlist entry. Frames are NEVER dropped: backpressure is
handled by waiting for the consumer to drain, not by discarding (a lost
chunk corrupts a transfer). Non-stream actions emit a single frame.
- POST /api/debug/upload — stream a browser-selected file to a temp path
on THIS server (the bot runs server-side, not on the operator's
machine), so the path can feed a send action's file/image/record/video
param. Raw bytes in the body (no multipart buffering), ?filename= for a
readable suffix, streamed to disk under os.tmpdir()/snowluma-debug-
upload with a 4 GiB cap and a 24h TTL sweep.
instance.invokeStream() delegates to ApiHandler.processStreamRequest.
Pure logic (filename sanitisation, fenced size-capped disk sink, the
stream-invoke Response builder) lives in debug-tools.ts with 12 unit
tests. Adversarially reviewed: path traversal is robust (19 hostile
filenames all fenced), frames provably never dropped, auth runs before
any disk write. Fixed a High found in review — the write stream had no
'error' listener (an async ENOSPC/EIO during flush would crash the whole
process) and a late-open race could leak a partial file; both now raced
into the awaits and cleaned up after 'close'.