Add headless mode flag and fix queued sends; verify live end-to-end

This commit is contained in:
agent
2026-09-11 13:29:43 +02:00
parent 2830bd1ade
commit 2a15f057df
4 changed files with 30 additions and 6 deletions
+21 -1
View File
@@ -55,6 +55,26 @@ direction.
- Discord deletes the stored token when it thinks devtools opened; keep Vesktop's devtools detector
patch (`src/renderer/patches/devtoolsFixes.ts`) so this cannot cause random logouts.
## Headless run (verified 2026-09-11)
The real client has been run with no visible window and successfully driven end-to-end:
```sh
Xvfb :99 -screen 0 1280x720x24 &
DISPLAY=:99 VESKSLOP_HEADLESS=1 \
VESKSLOP_TOKEN_FILE=~/.config/veskslop/token \
electron . --no-sandbox --disable-gpu --disable-dev-shm-usage
```
- A display server is still required; Xvfb works.
- `VESKSLOP_HEADLESS=1` skips the first-launch tour so the main window is created directly.
- The token file is seeded before the client boots (verified: logged in with the puppet account).
- Outbound sends must pass `waitForChannelReady = false` (`sendMessage(channelId, data, false)` in
`src/renderer/ipcCommands.ts`). Otherwise the client's MessageQueue waits forever for channels
that are never opened in a headless session.
- `message:read` is cache-only; channels the client never opened return zero messages. A
fetch-based read (through the client) is still to be added.
## How to proceed
**Current milestone — puppeting bridge (testable without Electron).**
@@ -101,7 +121,7 @@ MCP clients (agents) ──Streamable HTTP──▶ veskSlop control daemon
## Open questions
- Headless Electron strategy: Xvfb vs offscreen rendering, and what to strip to lighten the client.
- What to strip to lighten the client once the daemon path is stable.
- Voice/video: what the client's native stack can be driven to do without UI interaction.
- Scope and auth model of the future remote proxy client and web debug UI.
- How much of the Vesktop Electron tree gets deleted once the daemon is self-sufficient.
+4
View File
@@ -79,6 +79,10 @@ sanctioned live-send target, user **zzib** (`TEST_DM_USER_ID` overrides).
- QR login works end-to-end; the token in `.token` came from a phone scan.
- MCP bridge path: `initialize`, `tools/list`, `get_state`, `send_message`, `read_messages`,
`send_dm` all round-trip correctly through the mock.
- **Live end-to-end (2026-09-11)**: a real Vesktop client was run headless under Xvfb with the
token seeded from `~/.config/veskslop/token`. `get_state` returned the real account and 7 guilds,
and `send_dm` delivered a real DM to zzib (`sent: true`). Cache-only `read_messages` on that
never-opened DM returned zero messages, as expected.
- Pacing observed live: consecutive commands reached the mock **592 ms**, **1862 ms** and
**1439 ms** apart — inside the 500–2800 ms window.
+3 -3
View File
@@ -129,10 +129,10 @@ if (!app.requestSingleInstanceLock({ IS_DEV })) {
}
async function bootstrap() {
if (!Object.hasOwn(State.store, "firstLaunch")) {
createFirstLaunchTour();
} else {
if (process.env.VESKSLOP_HEADLESS === "1" || Object.hasOwn(State.store, "firstLaunch")) {
createWindows();
} else {
createFirstLaunchTour();
}
}
+2 -2
View File
@@ -76,7 +76,7 @@ onIpcCommand(IpcCommands.MESSAGE_SEND, async (data: { channelId?: string; conten
throw new Error("message:send requires { channelId, content }");
}
await sendMessage(data.channelId, { content: data.content });
await sendMessage(data.channelId, { content: data.content }, false);
return { channelId: data.channelId, sent: true };
});
@@ -115,6 +115,6 @@ onIpcCommand(IpcCommands.DM_SEND, async (data: { userId?: string; content?: stri
if (!channelId) throw new Error(`No DM channel for user ${data.userId} yet`);
await sendMessage(channelId, { content: data.content });
await sendMessage(channelId, { content: data.content }, false);
return { userId: data.userId, channelId, sent: true };
});