dsh-pve-vm 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,72 @@
3
3
  All notable changes to this plugin are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versions follow [SemVer](https://semver.org/).
5
5
 
6
+ ## [0.2.0] — 2026-10-13
7
+
8
+ Watch the agent work: a live VM screen in the right sidebar of the web UI.
9
+
10
+ ### Added
11
+
12
+ * **The “VM screen” tab** — a right-sidebar panel that streams the frames the agent sees. The frames
13
+ come from the agent's own RFB session, so no second VNC session appears and the `qm vncproxy`
14
+ password is never reset; captures and tools share one session queue. Watch-only by design: the
15
+ panel injects nothing into the guest.
16
+ * **`viewAutoOpen`** (off by default) — reveal the tab by itself when the agent touches a console.
17
+ * **`viewFollowAgent`** (on by default) — the panel follows the VM the agent works with; a VM chip
18
+ pins it by hand. Watching never counts as agent work: frame requests suppress the activity mark
19
+ (`withConsole(..., { mark: false })`), so the panel cannot chase itself.
20
+ * **`viewFps`** (2) and **`viewScale`** (2) — frame pace and resolution divisor, both changeable on
21
+ the fly inside the panel.
22
+ * **Host routes** `/pve-vm/console/state`, `/frame` and `/release` on the harness web server,
23
+ registered only when the composition has a `webServer`. They carry a loopback-only fence: Host must
24
+ be loopback, a present Origin must match Host, `sec-fetch-site: cross-site` is refused and POST must
25
+ be `application/json`. A harness bound to a LAN therefore keeps the panel dark.
26
+ * `RfbClient.waitForUpdate({ idleOk: true })` — silence at a message boundary means "the screen did
27
+ not change" instead of an error, while a timeout inside a message stays fatal.
28
+
29
+ ### Fixed
30
+
31
+ * **`inputChannel` and `sendkeyDelayMs` never reached the tools.** Both keys were missing from
32
+ `CONFIG_KEYS`, so `snapshot()` dropped them and every run silently used the built-in `auto`/80 ms
33
+ defaults. The form accepted the values, the code ignored them.
34
+
35
+ ## [0.1.1] — 2026-10-13
36
+
37
+ Linux guest support. Everything below was found by running the plugin against a real RedOS 8
38
+ guest with a MATE desktop, not by reading the code.
39
+
40
+ ### Fixed
41
+
42
+ * **Guest OS detection rejected almost every Linux distribution.** The guest agent reports `id`
43
+ from `/etc/os-release`, and only IDs containing `linux` were accepted — RedOS reports `redos`,
44
+ Debian `debian`, Ubuntu `ubuntu`. All of them landed in `other` and every `vm_*` tool answered
45
+ *running a shell command is not supported for guest OS «other»*, so the whole guest-agent half
46
+ was dead on Linux. Windows was unaffected (`mswindows`), which is why it went unnoticed.
47
+ Windows is now matched first, Linux by `id`/`name`/`pretty-name`, and a non-Windows guest that
48
+ answered `get-osinfo` with a non-empty `kernel-release` counts as Linux.
49
+ * **`vm_type_text` typed nothing on a GDM desktop.** The built-in typing script used
50
+ `$home/.Xauthority`, which does not exist when GDM owns the session — RedOS and RHEL keep the
51
+ file at `/run/user/<uid>/gdm/Xauthority`. `xdotool` then talked to the X server with no
52
+ authorization at all, failed with *No protocol specified*, and the text never appeared. The
53
+ script now tries `~/.Xauthority`, then the GDM path, then any `/run/user/<uid>/*/Xauthority`.
54
+ * **`vm_type_text` could not submit text.** `text=$(… | base64 -d)` — command substitution strips
55
+ trailing newlines, so text ending in `"\n"` was typed without pressing Enter and the shell
56
+ prompt never came back.
57
+ * **Console errors hid the actual cause.** When the node's `sshd` does not accept `AcceptEnv`,
58
+ `LC_PVE_TICKET` never arrives and `qm vncproxy` exits with a precise
59
+ *LC_PVE_TICKET not set, VNC proxy without password is forbidden* — but the RFB reader saw EOF
60
+ before ssh had finished exiting, so only *the VNC stream was closed by the node* surfaced. The
61
+ ssh stderr is now reported (after a short grace period), both during the handshake and when a
62
+ live session dies mid-way.
63
+
64
+ ### Known limitations
65
+
66
+ * Proxmox applies a **default 5-second timeout to QMP commands** (`PVE/QMPClient.pm`), and
67
+ `guest-exec` waits for the guest agent to acknowledge. While a previous long command is still
68
+ running inside the guest that acknowledgement arrives too late and the call fails with
69
+ *qmp command 'guest-exec' failed - got timeout*. On an idle guest 10 out of 10 calls complete
70
+ in about a second. `vm_exec` is currently synchronous and does not retry.
71
+
6
72
  ## [0.1.0] — 2026-10-13
7
73
 
8
74
  Initial public release.
package/README.md CHANGED
@@ -30,6 +30,9 @@ agent ──► plugin ──► ssh ──► node: qm vncproxy ──► unix
30
30
  * **Clipboard** — `console_paste` writes UTF-8 text into the guest clipboard over RFB and presses
31
31
  Ctrl+V; `console_clipboard` reads the guest clipboard back, so text can leave the VM without OCR.
32
32
  * **A settings card** in the web UI (English and Russian labels), backed by the profile patch layer.
33
+ * **A live screen panel** — the “VM screen” tab in the right sidebar shows the frames the agent
34
+ sees, taken from the very same VNC session, while the agent works. It follows the agent's VM
35
+ automatically or stays on the VM you pick by hand. Watching only: the panel never clicks or types.
33
36
  * **Zero runtime dependencies** — RFB, DES authentication, keysym tables and PNG encoding are
34
37
  implemented in this package on top of `node:crypto` and `node:zlib`. There is nothing to install
35
38
  on the host, and nothing to install on the node either.
@@ -95,6 +98,10 @@ layer, and the same values can be edited there by hand:
95
98
  sessionIdleMs: 300000
96
99
  guestTimeoutMs: 60000
97
100
  guestTypeCommand: '' # script for typing non-ASCII text into a Linux guest
101
+ viewAutoOpen: false # open the "VM screen" tab by itself when the agent touches a console
102
+ viewFollowAgent: true # the panel follows the VM the agent works with
103
+ viewFps: 2 # frame pace in the panel, 1–4
104
+ viewScale: 2 # frame resolution divisor: 1 native, 2 half, 4 quarter
98
105
  ```
99
106
 
100
107
  **First run checklist**
@@ -137,6 +144,33 @@ Every action tool accepts a common tail: `observe` (return a frame after the act
137
144
  | `vm_type_text` | `vmid`, `text`, `settleMs?`, `observe?` | Type text through the best available channel: ASCII → console, non-ASCII → the user session on Windows / `xdotool`–`wtype` on Linux. |
138
145
  | `vm_status` | `vmid?` | Is the VM allowed, does the agent answer, which OS, are there open console sessions. |
139
146
 
147
+ ## Watch the VM screen live
148
+
149
+ The **VM screen** tab in the right sidebar shows what the agent sees while it works. Add it from the
150
+ sidebar guide, or let `viewAutoOpen` reveal it the moment the agent touches a console.
151
+
152
+ * The frames are taken from the agent's own RFB session — **no second VNC session appears** and the
153
+ `qm vncproxy` password is never reset under the agent's feet. Captures and the agent's tools share
154
+ one session queue, so they never tear the stream for each other.
155
+ * `viewFollowAgent` keeps the panel on the VM the agent is working with; clicking a VM chip pins the
156
+ panel to that VM instead. `viewFps` (1–4) and `viewScale` (1 / 2 / 4) control the pace and the
157
+ resolution, and both can be changed on the fly inside the panel.
158
+ * The panel is **watch-only**: no clicks, no typing, nothing is injected into the guest. A blank
159
+ frame is called out explicitly — the guest blanked its display or is locked.
160
+ * Frames are served by the plugin's own routes (`/pve-vm/console/frame`, `/state`, `/release`) on the
161
+ harness web server, behind a loopback-only fence: a request is answered only when the `Host` is
162
+ loopback (`localhost`, `127.0.0.0/8`, `[::1]`), a present `Origin` matches `Host`, and
163
+ `sec-fetch-site: cross-site` is refused. If you bind the harness to your LAN, the panel stays dark
164
+ by design — the same fence the harness applies to `/api`.
165
+ * **The fence is loopback, not the harness token.** These routes live outside `/api`, so the
166
+ harness URL token does not cover them (the browser-skill overview works the same way). Any
167
+ process on the same machine can therefore pull frames or call `/release`; that is the accepted
168
+ price of loading frames without a token on a single-user box. Serve VMs you would not mind a
169
+ local user seeing, and harden with a harness-issued token if that is not acceptable.
170
+ * The console session stays open while the panel streams (the idle reaper does not take it); the
171
+ panel's “disconnect console” button closes it immediately. Streaming pauses by itself when the tab
172
+ is hidden.
173
+
140
174
  ## How it works
141
175
 
142
176
  * **Transport.** A VM has no TCP VNC port: QEMU listens on the unix socket