29 KiB
CURRENT.md — session state
Last updated: 2026-09-16
This file tracks the work state. Read it first. Update it before you stop.
Current focus (broad)
The text layer is done. Voice actions and audio work. The local LLM harness (Veskslop agent, Go + Eino) is scaffolded and smoke-tested; the daemon-side task push is next. The web UI has a first mobile draft at 390px.
In progress
-
Media archive project, branch
media-scrape-projectonveskslop-t. Design approved 2026-09-16 (docs: media.md, media-scraper.md, slopbot.md, media-mcp.md). Decisions: scrape the channel the command is sent in; slopbot (discord.js bot core) for guild channels and the puppet core bridge for group DMs; one self-contained SQLite + media dir per scrape undermedia-scrape/db/(gitignored); links indexed by default, downloads optional, third-party embeds never downloaded; 30 GiB default limit with a 90% warning and 5% no-delete overrun, usage bar and clean button in Settings → Bot; Media and Bot top-level tabs; MCP hooks designed for C8. The slopbot scaffold (config loader, invite helper) and its discord.js dependency were added 2026-09-16. C5 smoke test:/api/media/statusand/api/mediaanswer on a daemon with no core.Checkpoints, push after each:
- C1 design docs, AGENTS/INDEX pointers,
.gitignore - C2 core message shape: embeds, forwards, attachment metadata
- C3 media-scrape store and puppet walker, tests
- C4 slopbot backend, tests
- C5 daemon wiring:
scrape mediacommand, MediaService,/api/media/* - C6 Media and Bot tabs in the Web UI
- C7a storage limits, retention, warning/confirm flow, storage and clean API
- C7b Settings group: limit field with info hover, usage bar, clean modal
- C8 MCP service hooks (
scrape_media, scrape reads, storage, clean; reads inrun_commands) - C10 downloader: attachment and sticker bytes, hash-addressed, serial, size-capped, retries;
embeds excluded in SQL; local files served at
/api/media/<scrape>/<item>/fileand preferred by the Media tab. Live run 2026-09-16: scrape...-20260916083401fetched the 784 KB attachment; storage counted actual bytes. - C11 preview caching: Discord-hosted image/GIF previews (WebP 320px, hash-addressed, 5 MiB
cap, one attempt) run after every walk in both modes. Live run
...-20260916090722:previewed: 2; thumbs served over/api/media/<scrape>/<item>/thumb(200). - C17 Media grid and viewer. Embed videos now render their cached thumbnail with a play
icon; a Metadata dropdown picks banner fields (User, Type, Date, Source, Resolution, Length);
nothing shows until Load media; the status row reads
downloaded A/B | cached C/D (excludes downloaded) | N loadedfrom/api/media/stats; clicking a tile opens a viewer with wide-below / vertical-right metadata, full link with an external-site warning, View full, and an embedded player for YouTube/Vimeo/Twitch/Streamable when possible. - C16 Message counting and date-bounded scrapes. Discord search (
total_results, one guild request, slopbot only) powersPOST /api/media/countand MCPcount_media_messages; the confirm dialog has From/To dates plus a Check Message Count button; the walk honorsafterId/beforeId;estimatedMessages,fromDate, andtoDateland in the scrape meta and History. Live: count 17 all / 17 after 2026-09-16 / 0 before, DM returns a reason, and a from-dated scrape stored 17 messages withestimatedMessages: 17. - C15 Scrape UX: custom download destination (absolute path per scrape; files there,
previews local, embed thumbnails copied to both, custom files not served), confirmation popup
before Links/Download, New scrape/History right-aligned with New scrape furthest right, modal
X and backdrop close, active-only progress strip, and a Bot error feed with a red unseen badge
that clears on open plus a Clear button. Live run
...-20260916095029: 6 files and 7 embed thumbnails in/tmp/opencode/media-dest/<id>/, 11 local previews, 0 local files,localUrlnull; error record and clear verified over the API. - C14 Links-mode embed thumbnails, item metadata, filters, and the scrape popup. Metadata:
poster (
author_id/author_name),posted_at,width/height,aspect_ratio, with anffprobefallback after downloads. Filters: poster, aspect preset (vertical/square/4:3/16:9/ 21:9/other), resolution preset (SD/HD/FHD/QHD/4K+/unknown, by shortest side), and a posted-date range. UI: scrape popup with Server and Group-chat trees (server -> channels, each with Links/Download), History popup (time, duration, pages/messages, media, bytes, files, previews), live progress pulse, full-window view. Live run...-20260916094120: 11 previews in links mode (embed thumbnails included), poster zzib, a 9:16 short stored as 0.563, and the 16:9 filter matched. - C13 Embed thumbnails in download mode: embed rows cache their stored thumbnail URL;
the full third-party media is never fetched. Live run
...-20260916091458on the test channel: 6 files downloaded, 11 previews cached (YouTube, Reddit, Tenor, TikTok, X). Also fixed the state transition: the job staysrunningthrough download and preview passes so clients do not seedoneearly. - C12 Media tab filters and full-window layout: kind chips, source filter
(attachment/sticker/embed), source-channel filter, and the view now fills the window below the
header and call strip. API/MCP gained
sourceandchannelIdonquery_media. Smoke-tested over/api/media?source=and?channelId=. - [~] C9 live verification. Slopbot path verified live on 2026-09-16: login Slopbot#5775, guild
177931418704281600. Test channel links scrape: 17 messages, 4 images + 13 videos, 0 errors; download scrape fetched the 784 KB attachment. Large channelgernal_chut: 11 pages, 1088 messages, 96 images + 1 GIF + 203 videos, 0 errors (no 429 at the current pacing). Puppet path verified live on a group DM (zıbb & fred) and a 1:1 DM: backendpuppet, done, 0 errors. The puppet field check is done too: the group DM returned attachments plus mapped embeds (YouTube, Instagram), and the large slopbot channel produced YouTube, Gfycat, Imgur, Steam, and Twitch embeds alongside attachments. Still owed: a stress walk on the second guild (559346566754729995, channel559346566754729997).
- C1 design docs, AGENTS/INDEX pointers,
-
Flextalk voice profile, end to end. The profile window is a movable panel that recenters on each open; it carries the character sliders with engine defaults and +/-2 snapping, five named presets, radio-kit effects (high-pass 40-400 Hz, echo 0-100, word mode with a gap in ms), and test lines (Attention, Laboratory, Critical, Health, Radiation, plus two custom lines in
localStorage) that post the current profile straight to/api/audio/speak. Fixed the test buttons reading a stale expression from saved state; they now read the slider like Speak does. -
Root cause of the profile never reaching the engine (2026-09-15): the wrapper wrote the Flextalk
.regwithecho, and dash'sechotreats\2in...\Watson\2.0\...as an escape, which corrupted the key sowine reg importwas a silent no-op (it still returns 0). The wrapper now usesprintf, validates that every profile value is numeric, and rejects a.regcontaining control characters. Documented in TTS-DESIGN.md. -
The Veskslop agent lives in
agent/(Go 1.25+, Eino v0.9.19, remote llama.cpp over the OpenAI-compatible API, daemon tools over MCP Streamable HTTP). It is disabled by default (VESKSLOP_AGENT_ENABLED=1), takes tasks onPOST /task, and logs every tool call. Smoke-tested on 2026-09-14: 28 tools loaded, one ReAct round, final message returned. The daemon does not push tasks to it yet. Plan and env vars: docs/agent.md. -
Remote agents use the daemon's MCP endpoint directly (query state, send notifications). A notification tool set and a documented LAN/Tailscale bind are planned.
-
Mobile web UI first draft: four tabs (Users, Chat, Call, Activity), one pane at a time, 44px targets, tab badge, call bar pinned, Activity becomes the full console. Verified headless at 390x844. Next: transcript and TTS box in the Call tab, per the 1c design.
-
Fixed a pre-existing web UI bug:
loadDmscalled the nonexistentrenderDirect, so the DM rail only rendered from other paths. It now callsrenderDmRail(). -
Fixed the speaking marks: the core turned Discord's numeric
speaking: 0intotrue, so the equalizer bars never stopped. The bars now fade in on start and decay out on stop (500 ms), with a 2.5 s idle timeout for missed stops; a mute or channel change ends the state at once. Muted members show no bars. -
Browser audio: Client audio now runs the browser unlock in the click (microphone permission, AudioContext created and resumed, output sink applied) before it starts listening. Verified headless: 52 PCM frames played through a running context. The button label no longer wipes the icon markup.
-
Capture routing: nothing moved the client's playback streams into
veskslop_capture, so the browser monitor streamed digital silence. The audio manager now adopts matching streams (VESKSLOP_AUDIO_CLIENT_MATCH, defaultvesktop,electron,chromium,discord) on device setup, monitor start, and every 5 s while listening or capturing. -
LAN audio verified end to end on 2026-09-14: a tone stream on
veskslop_ttswas moved toveskslop_capture(daemon log:moved stream 81 to veskpop_capture) and the browser, connected tohttps://192.168.0.178:18801/, played 256+ PCM frames with a peak of 4096 (a stream already on the capture sink peaked at 29274). -
Speaking bars are always in the layout now:
scaleY(0)and transparent when idle, they scale up with the fade-in at start, run the equalizer animation while speaking, and stay on until an explicit stop, a mute, or a channel change. Live data showed repeatedspeaking: truepulses and no stop events inside an 8 s window, so the idle decay is removed: bars stick rather than disappear. The fade runs the equalizer inside a shrinking container, so the amplitude drops smoothly instead of the block collapsing at full height. The core treats a missingspeakingflag as a start pulse and acceptsuser_id. Core-side changes need a rebuild plus a client restart. -
Added
poc/scripts/audio-doctor.sh(pnpm audio:doctor): default sink, sink inputs, audio status with adoption counters, a 5 svolumedetectonveskslop_capture.monitor, and a live event peek. Run it in a real call to trace a silent browser monitor. -
Live doctor output (2026-09-14): the
vesktop/electronstreams sit onveskslop_capture(sink 40) already,adopted: 0, andVOICE_SPEAKINGevents arrive. Client audio routing is therefore in place. Bot TTS works. Capture was off (capturing: false), so the transcript was empty; Vosk transcribes correctly when invoked with the model env. -
Live call audio works now (the user confirmed TTS is audible and the silent call was partly a per-user volume set to zero for some members, plus another issue). The
volumeread returnsnullbecause this client build exposes noUserVolumeStore; the setter reportsNOT_SUPPORTED. Volume is not a usable control here. -
External TTS engine support (external project, first target is a command wrapper): the contract lives in
TTS-DESIGN.md. Veskslop now has nullabletts.speed(0.5-2.0) andtts.pitch(-12..+12) preferences, per-line overrides on/api/audio/speakand MCPspeak,{speed}/{pitch}placeholders,VESKSLOP_TTS_SPEED/_PITCHexports, and speed/pitch sliders in the TTS panel. A custom command advertises its voices throughVESKSLOP_TTS_VOICES; the espeak list is hidden while a custom command is set. Verified with a wrapper that logged argv and env: preferences and overrides both arrive. Reading the engine'sGET /voicesfor the HTTP provider remains future work. -
The MicroslopSam engine project is now part of the tree in
microslopsam/(copied from the separate project; itsbuild/output and voice data stay ignored). The wrapper was renamedsetup/vekslop-tts.sh->setup/veskslop-tts.shand every reference and configuration export was fixed. The engine also corrected the Flextalk pitch finding (REG_DWORD bug), so Keith Bell and Kerry Watson now support pitch and carry nono-pitchflag. -
Implemented the Flextalk Expression profile parameter end to end:
VESKSLOP_TTS_VOICESmarks the two voices asexpression=59/expression=100; the wrapper writes the registry value before synthesis; Veskslop hastts.expression(0-100 nullable), the{expression}placeholder,VESKSLOP_TTS_EXPRESSION, an HTTP/MCP field, and an Expression slider that appears only for those voices (default shown until moved, null leaves the registry untouched). Verified: 0/59/100 produce distinct audio, the registry reaches 0x14 for 20, and the UI shows 25/100/default states. -
Fixed the null-value coercion in the preference normalizers (
Number(null) === 0turned a cleared speed/expression into 0.5/0); unset now stays null. -
Renderer-down handling polished: the bridge logs one warning per outage instead of a stack per polled command, and the
navigator.languagesrequest catches the new timeout rejection instead of raising an unhandled promise warning. (TheexitCode: 9renderer kills in the live log were client restarts; the daemon's retry handled the startup race as designed.) -
The TTS panel has speed presets (0.5x, 0.75x, 1x, 1.25x, 1.5x) under the fine-adjust slider, and the pitch control is hidden entirely for voices that report
supportsPitch: false. (Flextalk voices no longer report that; see the pitch correction above.) Verified: preset clicks update the slider/label and save. -
TTS playback has a lead-in of silence before each line (
VESKSLOP_TTS_LEAD_MS, default 150 ms, max 2000, added withadelayinffplay; status reportsttsLeadMs) so Discord's voice pipeline is settled before the first syllable. Verified on the sink monitor: first sound at 0.02 s with 0 ms and 0.62 s with 600 ms. -
Speech settings have a Start engine button that warms the selected engine (
POST /api/audio/tts/warm, MCPstart_tts_engine): it runs one throwaway synthesis so the command engine's Xvfb/Wine/host are up. Verified with MicroslopSam: cold 1732 ms, warm 505 ms, and the UI shows "MicroslopSam ready in 835 ms". The warm text is a full sentence because their two-passloudnormfails withinfon very short input. -
Startup and crash robustness:
sendRendererCommandnow rejects immediately when the client renderer is destroyed or crashed, and times out after 15 s, so a dead frame no longer logs Electron's "Render frame was disposed" stack once per poll. Device auto-connect retries up to 10 times every 2.5 s, because the bridge socket is ready before the renderer's modules load (the live error wasReflect.get called on non-objectfrom a lazy lookup at startup). Verified with the mock: attempt 1 failed, attempt 2 set the devices. -
TTS engines are selectable: the Speech settings and the TTS panel list espeak-ng, the command engine (
VESKSLOP_TTS_NAME, default "custom engine"), and the HTTP API; unavailable entries are disabled and Auto resolves to the command engine first. Selecting an engine switches the voice list (/api/audio/tts/voices?engine=) and hides the language row for single-language engines. The TTS panel now has a voice select next to the speed/pitch sliders. Fixed a slider bug where a null speed preference (Number(null) === 0) clamped the slider to 0.5 and then persisted it. -
TTS controls: the TTS panel has a working duck on voice toggle (stored in preferences, reported as
duckEnabled). Ducking was verified live with the mock: theveskslop_ttssink drops to 30% while TTS plays and someone speaks, and returns to 100% after. The Transcript tab in the console has Start and Stop buttons; the status line showscapturing/capture off. The espeak-ng voice list now includes them1–m7,f1–f5,croak, andwhispervariants (<voice>+<variant>), so each language has about 15 voices instead of one. -
Speaking state is now authoritative: the daemon polls the core's
voice.speakingaction (ChannelRTCStore.getSpeakingParticipants) every second and synthesizes the missing stop/start events; dispatcherVOICE_SPEAKINGhints feed the same set so stale users clear. This fixes the bars sticking for everyone and the transcript attribution, which credited the first member because speaker intervals never closed. Verified with the mock: aspeaking: trueevent cleared within one poll. -
The client streams sit uncorked, unmuted, and at 100% on
veskslop_capture, yet the monitor is silent. GatewayVOICE_SPEAKINGevents do not prove inbound RTP, and the TTS path only proves outbound, so the remaining suspect is the client's inbound voice state (stuck deafen or a broken voice connection), not the sink routing./healthnow returnsselfIdso the bot's own member flags can be picked out of the voice member list; the doctor prints it. -
Client output is still silent at the sink (
-91 dB) although the streams are uncorked, unmuted, and at 100%, and a headless Chromium tone into the same sink measured-7.4 dB. Added per-user volume reading and writing to find Discord-side silencing:user.volume/user.setVolumein the core,/api/voice/volume(GET and POST), MCPget_user_volume/set_user_volume, andvolumein every voice member. A volume of 0 (or an output volume of 0) silences a user even when Pulse shows a healthy stream.voice.joinalso clears persisted self-deafen now. The doctor prints output and per-user volumes. -
Voice audio is implemented: daemon-managed virtual devices (
veskslop_tts,veskslop_mic,veskslop_capture), TTS providers (local command + HTTP API), queued playback throughffplay, ducking onVOICE_SPEAKING, and WAV capture. The daemon startspulseaudiowhenpactlcannot reach a server and reports install hints. Verified live on 2026-09-13 after installingpulseaudio/ffmpeg: engine ok, devices created, PCM streamed. -
The Web UI serves self-signed HTTPS (
openssl, stored under the state dir) so browser media APIs have a secure context.WEBUI_TLS=0disables it. -
Capture and transcript are implemented: ffmpeg reads the client output, a VAD cuts it into utterances, each becomes a WAV plus a transcript entry with the dominant speaker, and a pluggable STT hook (local command or HTTP API) fills the text. Live events feed the console Transcript tab;
get_transcriptand/api/transcriptread the store.VESKSLOP_CAPTURE_AUTO=1records calls automatically. Verified end to end with real PulseAudio on 2026-09-13. -
STT uses Vosk (CPU, light):
poc/scripts/stt-vosk.pyplus a small model inpoc/models/(auto-detected). Verified with a TTS→capture→Vosk round trip. Other providers remain available viaVESKSLOP_STT_CMD/_API_URL. -
TTS defaults to
espeak-ngwhen installed; engine and voice live in Settings → Client → Speech (daemon preferences, so all browsers share them). -
Preferences moved to
<state dir>/preferences.jsonand no longer depend on the core, so guild visibility saves even while the client is down. API responses sendCache-Control: no-store. -
Browser audio has an Enable browser audio button that requests the microphone permission, which browsers require before listing output devices.
-
Browser microphone puppeting (talking through the bot) is next; it needs a mix sink, a
pacatinput, and echo handling. -
The web UI follows the Nocturne design handoff (
poc/WebUIMockup.zip). The 7a shell is implemented and rendered headless for review: header, call strip, three-column body, composer, and the agent console. -
Takeover is a client-side read-only gate. Off, the composer is locked. Manual sends are logged.
-
Core process control is implemented and verified live: the daemon starts the client under auto-Xvfb, the bridge connects, and stop exits cleanly.
-
Web QR login is implemented. The session reached
pendingagainst the real gateway; a full phone approval is pending.
Done
-
Validated core bridge: protocol, actions, validation, structured errors, hard outbound rate limit.
-
Auth persistence: pinned
DATA_DIR, token file, preload seeding,VESKSLOP_HEADLESS. -
Daemon on the core-bridge transport. CDP stays as a development fallback.
-
Trusted-only triggers: DMs and direct mentions. Trust tiers: user, trusted, admin.
-
Context: 15 messages or 4000 characters, time stamps, file names only, incremental fetch, tmp cache with a size limit, fetch cooldown.
-
Continuation window: a rushed follow-up joins the request context.
-
Response policy: origin channel or DM. Sensitive commands default to DM.
-
Persistent action log: triggers, commands, replies, errors, rotation, and secret redaction.
-
pollis paused (POLL_ENABLED = false). The parser and its tests stay. -
Human send pacing: first send of a burst waits 30-200 ms, later sends 500-2800 ms, and the burst resets after a random 5-20 s of quiet. Reads take 100-300 ms and do not reset the burst.
-
user.search: cache and guild-member lookup.voice.getsearches all guilds without aguildId. -
voice.overview: occupied voice channels and members for a guild. -
VOICE_STATE_UPDATEevents: normalized and pushed. Mock-verified. -
Voice actions:
voice.join(muted),voice.leave, andvoice.movein the core, with daemon routes (/api/voice/*), MCP tools, and Web UI wiring. Voice actions are paced 250-750 ms and written to the action log. The internal lookups were probed on the live client. -
Web UI voice rail: every voice channel is clickable. Clicking joins (muted); clicking the active channel leaves. Verified with the stateful mock.
-
Web UI startup: a Start server button appears left of Takeover while the bridge is down and hides when it connects. State reloads retry while the client boots, so guilds appear without a manual refresh. Core requests now time out after
VESKSLOP_REQUEST_TIMEOUT_MSinstead of hanging forever on a missing reply. -
Settings: "Visible guilds" stays top level; Audio sits under a collapsible "Client" group; Core process, Account login, and Action log sit under a collapsible "Server" group.
-
Web UI timestamps use the browser timezone by default; Settings → Client → Display can override it with any IANA zone. The choice is per browser (
localStorage). -
Web UI preferences: visible guilds persist server-side in the daemon state file (
/api/preferences), so every browser shares them. The Settings panel has an explicit Save button; selections filter the UI live and persist only on Save. -
DM list: recipients resolve from IDs through the user store, so names and avatars load.
-
Guild menu: fixed the light default button background.
-
Web UI: Nocturne design tokens in
poc/public/nocturne.css, the 7a debug shell, live guild and channel rail, messages, DMs, voice membership, agent console with a live event tab, and a Settings slide-over for core process, login, visible guilds, and the action log. Roadmap surfaces show as placeholders. -
Web UI data: opening a chat fetches the latest 50 messages into the client cache; attachments hotlink Discord CDN URLs and are never stored; avatars and guild icons load from the CDN with a monogram fallback; the page reloads itself when the bridge connects.
-
Web UI drafts: composer and TTS text persist per channel in
localStorageand survive re-renders, reconnects, and page reloads. -
Visible guilds: the Settings panel filters the guild switcher and picker.
-
Takeover: client-side read-only switch with
localStoragepersistence; manual sends are written to the action log withtype: "manual". -
dm.list: cached DM and group DM channels, exposed over MCP aslist_dms. -
Core process control: the daemon starts, stops, and restarts the client, reconnects the bridge after a restart, auto-starts Xvfb when
DISPLAYis missing, and stops what it started on exit. Override withVESKSLOP_CORE_CMD/ARGS; disable Xvfb withVESKSLOP_XVFB=0. -
Web QR login: the Settings panel runs the remote-auth session and renders the QR. The token file is written on approval and a running managed core restarts with the new token.
-
Unit tests cover the pacing policy.
pnpm lintandpnpm testTypespass at the root. -
Web UI navigation: Guild/DM scope tabs, a DM rail with unread badges (purple/orange, grey while Takeover is off), last-message times, favourite stars, and a People list of users seen in voice. Favourited users get presence and call tracking, grouped by call in In voice. Speaking shows level bars and bold names (2c marks for mute/deaf/video/share). The body dims while the bridge is down; the Start button becomes Refresh when the daemon is unreachable.
-
TTS settings are split into language and voice, grouped by base language with the requested languages pinned on top.
-
Text notifications are parsed: edits update the context cache, deletes drop from it, trusted reactions are logged, and replies are kept in the request context. A reply to a puppet message counts as a trigger without a mention. The web UI marks edited messages and replies.
-
Voice auto-leave is implemented: when the owner (zzib) leaves the bot's call, the bot leaves after
voiceLeaveDelaySeconds(default 30). A rejoin cancels it. Verified with the mock, including cancellation.stay @useris deferred. -
Audio verified end to end on 2026-09-14: TTS played into the virtual mic (recording
veskslop_micpeaked at -4.1 dB) and the browser monitor streamed PCM over the TLS WebSocket. -
Next: per-user limits and the collector account. The agent attachment (remote llama.cpp, MCP transport, event delivery) waits for a design discussion; the draft prompt has open issues to settle first.
Next (in order)
- Live verification:
voice.join/voice.leaveend to end,user.search,voice.overview,VOICE_STATE_UPDATE, and a full web QR login approval on the real client. - Agent: daemon task push for free-form triggers (commands stay local), tool allowlist, per-conversation serialization, and tests against a fake model.
- Collector account: read-only modules for voice membership fallback.
- Per-user rate limit and command cooldowns.
- Mobile web UI: transcript and TTS in the Call tab; verify on a phone.
Open discussions (need the user)
- Web UI auth. Accepted: no auth, LAN or Tailscale only. Revisit before any wider exposure.
- MCP event delivery. Decided for the local harness: the daemon pushes tasks to the agent's
POST /task. The transport details (retry, dedupe IDs, queue) are still open. Remote agents poll or call tools directly; they do not receive pushes. - Untrusted logging. Untrusted interactions are ignored at this time. Confirm whether to log untrusted DMs and mentions again.
- Privacy pass. Only token-shaped and
mfa.strings are redacted, and only in the persistent action log. A fuller policy (retention, cache clearing, message redaction) is deferred. - Live checks.
reaction.add,user.search,voice.overview, and the voice state event normalization are mock-verified only. The QR login session reachedpendingandcancelagainst the real gateway; the approval path needs the phone. Confirm on the first real-client run.
Blocked / needs input
- Slopbot: the bot is invited to the test guild and the intent is enabled. The token lives in
poc/.env(gitignored, mode 600) and will be replaced when the project goes to prod. The daemon must be restarted to pick it up. - The original repo
zib/Veskslophas a broken server-side git setup. Fetches and clones fail withbad pack headerbecause a script (/home/git/.sys_health_s3) runs on the Veskslop read path. The admin must fix it. Usezib/Veskslop-t(remoteveskslop-t) until then. That remote has no stored credential; use the one-off token URL inGITEA.md. Rechecked 2026-09-16: the original still fails withbad pack header. mainon the original server is68fc8bb(zib, "Upload files to docs/images"). It diverged from the localmain(24447f5). Reconcile after the server is fixed.
Where to look
- Backlog:
TODO.md - Design documents:
docs/INDEX.md - Design handoff:
poc/WebUIMockup.zip(screen 7a is implemented) - Agent rules:
AGENTS.md - Daemon code:
poc/src/ - Web UI code:
poc/src/webui.ts,poc/public/ - Core bridge code:
src/shared/puppetProtocol.ts,src/renderer/puppet/,src/main/puppetBridge.ts