@rubytech/create-realagent 1.0.673 → 1.0.676

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.
Files changed (26) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/docs/references/memory-guide.md +1 -1
  3. package/payload/platform/plugins/docs/references/platform.md +9 -5
  4. package/payload/platform/plugins/docs/references/troubleshooting.md +39 -27
  5. package/payload/platform/scripts/vnc.sh +115 -2
  6. package/payload/server/chunk-5YIXIF6C.js +726 -0
  7. package/payload/server/maxy-edge.js +420 -0
  8. package/payload/server/public/assets/admin-DQmUdTBa.js +352 -0
  9. package/payload/server/public/assets/{data-BffoLFjh.js → data-DVlvxbTt.js} +1 -1
  10. package/payload/server/public/assets/{file-CFwYpr7V.js → file-OY_hX2wu.js} +1 -1
  11. package/payload/server/public/assets/{graph-BTqqR6HN.js → graph-BDaM4Qer.js} +13 -13
  12. package/payload/server/public/assets/{house-DL5e4lPb.js → house-CgENfOCP.js} +1 -1
  13. package/payload/server/public/assets/jsx-runtime-Bu4vXoe7.css +1 -0
  14. package/payload/server/public/assets/{public-BJJlVuMu.js → public-Clp4VPwo.js} +1 -1
  15. package/payload/server/public/assets/{share-2-Bcn7Fo6t.js → share-2-RSIR3MmX.js} +1 -1
  16. package/payload/server/public/assets/{useVoiceRecorder-BqzlICMx.js → useVoiceRecorder-B0FI_hts.js} +1 -1
  17. package/payload/server/public/assets/{x-Dv8-a-IC.js → x-DKZ5NR3n.js} +1 -1
  18. package/payload/server/public/data.html +6 -6
  19. package/payload/server/public/graph.html +6 -6
  20. package/payload/server/public/index.html +7 -8
  21. package/payload/server/public/public.html +4 -4
  22. package/payload/server/server.js +660 -1518
  23. package/payload/server/public/assets/admin-CJCz3YFE.js +0 -362
  24. package/payload/server/public/assets/admin-kHJ-D0s7.css +0 -1
  25. package/payload/server/public/assets/jsx-runtime-DsAwO6-r.css +0 -1
  26. /package/payload/server/public/assets/{jsx-runtime-DYU20lw9.js → jsx-runtime-C_VUlXvu.js} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-realagent",
3
- "version": "1.0.673",
3
+ "version": "1.0.676",
4
4
  "description": "Install Real Agent — Built for agents. By agents.",
5
5
  "bin": {
6
6
  "create-realagent": "./dist/index.js"
@@ -84,7 +84,7 @@ Ask naturally:
84
84
 
85
85
  Maxy answers relational questions — "list all my people", "how many tasks do I have", "find the person with email X", "show me the 20 most recently created nodes" — via direct read-only Cypher against your Neo4j. This is faster and more precise than semantic search when the question is "the exact set where", not "things similar to".
86
86
 
87
- You can also open a visual view of your graph at any time from the burger menu → **Graph**. Click the **Filter** button in the toolbar to open the filter menu — it lists only the top-level entity types in your schema (Conversation, Person, Task, KnowledgeDocument, …), one row per type, showing your per-type node count and sorted so the most-connected types sit at the top. Child types (messages inside a conversation, sections inside a document), conversation channel variants (admin vs public), message role variants (user vs assistant), workflow execution plumbing (`ToolCall`, `WorkflowRun`, `WorkflowStep`, `StepResult`), and review signals (`ReviewAlert`) never appear as filter rows — you reach children by clicking the parent and exploring its neighbourhood, and review signals have their own dedicated surface in admin chat. Active rows render a force-directed map, coloured by label. Click a node to pivot into its 1-hop neighbourhood; click another node inside that neighbourhood to pivot again. A breadcrumb strip above the canvas shows where you are (`Filter › Conversation › AssistantMessage`). The **Back** control pops one level — three clicks in always undoes with three Back presses; the filter view is the irreducible root. Click the **×** inside the filter menu to clear your chip selection. Type in the search box to highlight matches; submitting a search also widens the filter to include any node types the hits belong to, so relevant matches render instead of disappearing into a "not in current view" banner.
87
+ You can also open a visual view of your graph at any time from the burger menu → **Graph**. Click the **Filter** button in the toolbar to open the filter menu — it lists only the top-level entity types in your schema (Conversation, Person, Task, KnowledgeDocument, …), one row per type, showing your per-type node count and sorted so the most-connected types sit at the top. Child types (messages inside a conversation, sections inside a document), conversation channel variants (admin vs public), message role variants (user vs assistant), workflow execution plumbing (`ToolCall`, `WorkflowRun`, `WorkflowStep`, `StepResult`), and review signals (`ReviewAlert`) never appear as filter rows — you reach children by clicking the parent and exploring its neighbourhood, and review signals have their own dedicated surface in admin chat. Active rows render a force-directed map, coloured by label. Click a node to pivot into its 1-hop neighbourhood; click another node inside that neighbourhood to pivot again. Clicking a Message shows its details in the side panel; the Conversation view stays put — you read sibling messages without losing the chain on canvas. A breadcrumb strip above the canvas shows where you are (`Filter › Conversation › AssistantMessage`). The **Back** control pops one level — three clicks in always undoes with three Back presses; the filter view is the irreducible root. Click the **×** inside the filter menu to clear your chip selection. Type in the search box to highlight matches; submitting a search also widens the filter to include any node types the hits belong to, so relevant matches render instead of disappearing into a "not in current view" banner.
88
88
 
89
89
  Conversations and Messages carry role/channel sublabels so you can read the chat topology by colour alone — admin vs public conversations and user vs assistant messages render in distinct shades on the canvas. The filter menu intentionally does not split them into separate rows — the base chip is the entry point; you see the variants as colours once you're inside a neighbourhood.
90
90
 
@@ -64,15 +64,19 @@ The chat input auto-grows as you type — it expands to fit your message and shr
64
64
 
65
65
  ## Admin Terminal
66
66
 
67
- The admin UI includes a live terminal surface that opens a real shell on your Pi in the browser, reached via the Software Update modal. Under the hood it's a WebSocket (`/admin/terminal/ws`) attached through `@xterm/xterm` to `ttyd` on `127.0.0.1:7681`, backed by a persistent tmux session named `maxy-pty`.
67
+ The admin UI has a single terminal surface one pipeline (a real GUI terminal emulator running on the Pi's VNC display `:99`, rendered inside an `iframe` of `/vnc-viewer.html`) with two entry points: the burger menu's **Terminal** button (opens a plain operator shell) and the Software Update modal's **Upgrade** button (opens the same iframe with `npx -y @rubytech/create-maxy@latest` already running in the spawned shell).
68
68
 
69
- The tmux session outlives admin-server restarts running an upgrade inside this terminal means you see the live shell output continuously, even through the admin server's own restart mid-upgrade. Closing the browser tab does not kill the running work; re-opening the Software Update window reattaches to the same session during an active upgrade and scrollback shows everything that happened in the meantime. Password-protected `sudo` prompts appear natively inside the terminal, and the password you type never leaves the Pi the admin-server proxy is a raw byte pipe that never inspects frame payloads.
69
+ The binary is chosen by target display. For the VNC virtual display `:99`, **xterm** is preferred because it honours `DISPLAY` directly with no IPC layer. For the native loopback display, **gnome-terminal** is preferred because the login session's `gnome-terminal-server` owns that display. (gnome-terminal cannot be used for the VNC display it's a D-Bus launcher that delegates window creation to the session's server, and the window would appear on the physical screen instead of inside the admin iframe.) Post-spawn, the launch pipeline asserts the window actually landed on the target display via `xdotool search --onlyvisible --class '.'` if anything went wrong (gnome-terminal D-Bus delegation, missing `xdotool`, broken X server), the 502 response body carries the exact diagnostic string inline in the UI instead of showing a black rectangle.
70
70
 
71
- The Software Update window mounts the terminal lazily: neither the terminal, its WebSocket, nor its black-backgrounded container render until you click Upgrade. Pre-click, the window shows a small "Ready to upgrade click Upgrade to begin." line and no network traffic flows. On click, the window mounts the terminal and sends the npx command via `RemoteTerminal.onReady`; the terminal output is the only upgrade observability surface. A background version poll flips to the success row when `installed === latest` and reloads the page. The upgrade command is dispatched the moment the WebSocket opens you won't see "terminal not ready" warnings on a healthy device. If the admin server cannot reach `ttyd`, the window renders an inline "Admin terminal not available" message with the exact re-install command and a Try again button. Closing the window does not kill the upgrade the tmux session keeps running upstream but the window itself starts fresh on every reopen: if you reopen during an in-flight upgrade, click Upgrade again and the scrollback shows the in-progress output.
71
+ **Software Update flow.** Click Upgrade in the modal the admin server kills any pre-existing terminal on `:99` (the upgrade must run in a fresh shell) and spawns the chosen binary with a command dispatcher: `xterm -e bash -c "npx …; exec bash"` or `gnome-terminal -- bash -c "npx …; exec bash"`. The trailing `exec bash` replaces the shell process after `npx` exits so the window stays open you can scroll through the full installer output and read the final "Open in your browser:" line at your own pace. The modal mounts the fullscreen VNC terminal overlay (the same surface the burger menu's Terminal button uses) and the operator watches the installer run at viewport height. A background poll of `GET /api/admin/version` every 5 seconds detects completion: the moment `installed === latest`, the modal flips to success and the page reloads two seconds later. Password-protected `sudo` prompts appear natively inside the terminal the password you type never leaves the Pi.
72
72
 
73
- Because the terminal is the only surface, it narrates its own state when something goes wrong. The moment you click Upgrade, the terminal echoes a timestamped `[upgrade] starting at <UTC> — shell+ws+tmux+xterm chain OK` line before the `npx` invocation begins that single line confirms the WebSocket, tmux session, shell, and xterm renderer are all working end-to-end. If 5 seconds then pass with no output from `npx`, the terminal itself writes a `[terminal] no bytes from upstream in 5s ws.readyState=…, bytesReceived=…, attempt=…` diagnostic into its own buffer, and repeats that narration every 30 seconds of continued silence. If even that narration never appears within ~10 seconds of click, the xterm renderer or its WebSocket is broken and reloading the admin UI is the fix. On the server side, `~/.maxy/logs/terminal.log` carries a `terminal-proxy-flow` heartbeat every 5 seconds while bytes are moving and every ~30 seconds while idle, with `clientBytes`, `upstreamBytes`, and `idleMs` fields — so a stuck upstream presents as `upstreamBytes` frozen and `idleMs` climbing, directly visible in a `tail -f` without any client-side evidence needed.
73
+ **Operator Terminal flow.** The burger menu's Terminal button uses the exact same pipeline, minus the pre-loaded command. Click Terminal the admin server spawns the same binary (no `-e`/`--` dispatcher, no `bash -c` wrapper) the iframe shows an empty shell. Closing the overlay with Esc or × kills the spawned PID on the Pi via `POST /api/admin/terminal/close`; reopening launches a fresh shell.
74
74
 
75
- The burger menu in the admin chat also includes a standalone **Terminal** entry next to **Browser**, but this surface is intentionally *different* from the upgrade-modal terminal. Clicking it spawns a real GUI terminal emulator on the VNC virtual display `:99` via the same `/vnc-viewer.html` iframe that Browser uses. The binary is chosen by target display: for the VNC virtual display, **xterm** is preferred because it honours `DISPLAY` directly with no IPC layer; for the native loopback display, **gnome-terminal** is preferred because the login session's `gnome-terminal-server` owns that display. (gnome-terminal cannot be used for the VNC display — it's a D-Bus launcher that delegates window creation to the session's server, and the window would appear on the physical screen instead of inside the admin iframe.) The header Terminal is isomorphic to the header Browser: one pipeline (launch endpoint → display spawn → display-membership assertion → noVNC iframe), one failure domain and deliberately decoupled from ttyd. Stopping `maxy-ttyd.service` leaves the header Terminal fully functional; only the in-modal upgrade terminal degrades. Closing the header Terminal overlay (Escape or the `×` button) kills the spawned emulator PID on the Pi via `POST /api/admin/terminal/close`; re-opening launches a fresh shell. Authorisation is inherited from the same `canAccessAdmin()` gate that wraps every `/api/admin/*` route. The launch endpoint logs to `~/.maxy/logs/terminal-launch.log` (script-level spawn/kill events, including `windowPresent=true` on success and `self-heal pid=... observed_windows=<n>` when a stale PID is respawned) and to `vnc-boot.log` via `vncLog('ensure-terminal', ...)` (Node-side state machine), mirroring the Browser's `ensure-cdp` observability shape. Any spawn failure surfaces verbatim in the 502 response body — no silent black rectangles.
75
+ **If you click Upgrade while an operator Terminal is open** (or vice versa), the pre-existing shell is killed and the iframe refreshes with the new shell. The upgrade is disruptive by designyou cannot graft an installer onto a shell that already has your env, cwd, and aliases.
76
+
77
+ **Error surface.** If the terminal emulator fails to spawn on `:99` (missing xterm, gnome-terminal broken, X server down), the UpdateModal renders the verbatim server-side diagnostic — e.g. `[terminal-launch] failed err="window absent from target display after spawn" pid=... display=:99 observed_windows=0 transport=vnc reason=upgrade` — and the Upgrade button re-labels to "Try again". The same string lands in `~/.maxy/logs/vnc-boot.log` under `action=launch-upgrade-failed err="..."`, so what you see in the modal and what your operator-visible diagnostic grep finds are identical.
78
+
79
+ **Authorisation** is inherited from the same `canAccessAdmin()` gate that wraps every `/api/admin/*` route.
76
80
 
77
81
  ## AI Content Provenance
78
82
 
@@ -93,54 +93,66 @@ If the initial Cloudflare login fails during setup, Maxy will fall back to askin
93
93
 
94
94
  ---
95
95
 
96
- ## Admin Terminal Stuck Disconnected After Upgrade
96
+ ## Software Update click shows an error instead of opening the terminal
97
97
 
98
- **Symptom:** The Software Update window's terminal goes blank, says "server restart detected reconnecting…" and never comes back, or the window sits empty after you open it.
98
+ **Symptom:** You clicked **Upgrade** in the Software Update modal, but instead of the VNC terminal overlay appearing, the modal shows a red error row like:
99
99
 
100
- **Check:** `sudo systemctl --user status maxy-ttyd` — the unit should be `active (running)`. If it is, the admin-server proxy should reattach automatically within a few seconds.
100
+ ```
101
+ [terminal-launch] failed err="window absent from target display after spawn" pid=1234 display=:99 observed_windows=0 transport=vnc reason=upgrade
102
+ ```
101
103
 
102
- **Fix:** Restart the unit:
104
+ …and the Upgrade button re-labels to "Try again".
103
105
 
104
- ```bash
105
- sudo systemctl --user restart maxy-ttyd
106
- ```
106
+ **What it means:** `POST /api/admin/terminal/launch-upgrade` returned a 502 — the admin server tried to spawn a real terminal on the VNC display `:99` with `npx -y @rubytech/create-maxy@latest` pre-loaded, but the spawn failed or the window never appeared on the target display. The modal's upgrade path uses the same pipeline as the header-menu Terminal; if the operator Terminal button works, the VNC stack itself is healthy and the fault is usually a missing dep or a stale binary. If the operator Terminal also fails, start with the "Header Terminal click shows an error alert" section below.
107
107
 
108
- This is safe — the tmux session survives the ttyd restart because `tmux new-session -A -s maxy-pty` is idempotent. Any upgrade or other shell command still running inside the session continues uninterrupted; ttyd simply re-attaches to it. If the session itself is wedged, you can kill it explicitly with `tmux kill-session -t maxy-pty` and the next attach will create a fresh one.
108
+ Step-by-step diagnosis — the error string in the modal is identical to the entry in `~/.maxy/logs/vnc-boot.log`, so you can grep it directly:
109
109
 
110
- **Terminal unit missing entirely?** If `sudo systemctl --user status maxy-ttyd` reports `unit not found`, the installer's ttyd provisioning step failed — typically on a Bookworm device where `ttyd` is not available via apt and the upstream download (or SHA256 check) failed at install time. Re-run `npx -y @rubytech/create-maxy@latest`; the operator-visible remediation command is also printed at install time in `~/.maxy/logs/install-*.log`.
110
+ ```bash
111
+ # 1. Find the latest upgrade launch attempt
112
+ sudo grep 'action=launch-upgrade' ~/.maxy/logs/vnc-boot.log | tail -10
111
113
 
112
- ---
114
+ # 2. See the script-level failure line (same string the modal shows)
115
+ sudo tail -n 30 ~/.maxy/logs/terminal-launch.log
116
+
117
+ # 3. Verify binaries are present
118
+ which xterm xdotool
113
119
 
114
- ## "Admin terminal not available" in the Software Update window
120
+ # 4. Confirm the VNC display itself is up
121
+ DISPLAY=:99 xdpyinfo >/dev/null 2>&1 && echo "display :99 ok" || echo "display dead"
122
+ ```
115
123
 
116
- **Symptom:** The Software Update window displays "Admin terminal not available. Re-run the installer from a shell: `npx -y @rubytech/create-maxy@latest`" with a Try again button, instead of the usual terminal area.
124
+ **Common upgrade-specific failures:**
117
125
 
118
- **What it means:** The admin server could not reach `ttyd` on `127.0.0.1:7681`. Either `maxy-ttyd.service` is not running, or it failed to install during setup.
126
+ - `err="no terminal emulator installed"` run `sudo apt-get install -y xterm xdotool` or re-run `npx -y @rubytech/create-maxy@latest` the installer provisions both as hard deps.
127
+ - `err="spawn detached but no terminal PID visible within 1s" ... reason=upgrade` → X server on `:99` is wedged; `sudo systemctl --user restart maxy-ui` cycles the VNC stack via `vnc.sh start`. Then click **Try again** in the modal.
128
+ - `err="window absent from target display after spawn" ... reason=upgrade` → the spawn succeeded but landed on the wrong display (Task 632 class). Re-run the installer to refresh the `resolve_terminal_bin` logic. If a stale `gnome-terminal` is hitting `:99` via D-Bus delegation, the installer's `xterm` fallback is the fix.
129
+ - `err="xdotool not installed — re-run installer to repair"` → Task 634 preflight. `sudo apt-get install -y xdotool` or re-run the installer.
130
+ - `err="VNC failed to start after recovery attempt"` → `Xtigervnc` itself is not coming up. Inspect `~/.maxy/logs/vnc-boot.log` for tigervnc startup lines; `ss -ltn '( sport = 6080 or sport = 5900 )'` should show both ports listening.
119
131
 
120
- **Fix:** Run the exact command shown in the error message from a shell on the device:
132
+ **Click Try again** after applying any fix the modal re-POSTs the launch-upgrade request.
121
133
 
122
- ```bash
123
- npx -y @rubytech/create-maxy@latest
124
- ```
134
+ ---
125
135
 
126
- Then return to the upgrade window and click **Try again**. The window re-probes `/api/health` and, once ttyd is listening, the terminal area mounts as normal. If the problem persists, check the boot log for `[ttyd] upstream NOT reachable on 127.0.0.1:7681` and follow the `maxy-ttyd` restart steps above.
136
+ ## Upgrade terminal opens but npx never runs
127
137
 
128
- ## Upgrade terminal stays blank after clicking Upgrade
138
+ **Symptom:** You clicked **Upgrade**, the VNC overlay opened with a visible shell prompt, but `npx -y @rubytech/create-maxy@latest` does not execute and the shell is idle.
129
139
 
130
- **Symptom:** You clicked **Upgrade**, the terminal area mounted, but the terminal looks empty.
140
+ **What it means:** The VNC spawn succeeded but the binary-specific command dispatcher (xterm `-e` or gnome-terminal `--`) did not forward the command. Given the installer ships `xterm` on `:99` by default (with D-Bus-safe dispatching), the likely causes are:
131
141
 
132
- **What you should see the terminal narrates itself.** Within a few hundred milliseconds of click, the terminal echoes a line like `[upgrade] starting at 2026-04-21T11:48:33Z — shell+ws+tmux+xterm chain OK`. That single line confirms the WebSocket, tmux session, shell, and xterm renderer are all working. Within ~2 seconds on a warm npx cache, the installer's own first line arrives on top of that.
142
+ - A stale binary override has re-pointed `resolve_terminal_bin` to `gnome-terminal` on `:99` despite the Task 632 fix.
143
+ - A shell the operator manually spawned (via the header Terminal) wasn't killed before the upgrade click, and `ensureTerminalUpgrade`'s pre-kill step silently failed.
133
144
 
134
- **If the terminal is silent for more than 5 seconds:** the terminal itself writes a yellow diagnostic like `[terminal] no bytes from upstream in 5s — ws.readyState=1, bytesReceived=0, attempt=1` into its own buffer, and repeats that narration every 30 seconds of continued silence. If those lines appear, the WebSocket and renderer are fine — the problem is upstream. SSH to the device and check `ttyd`:
145
+ **Check:**
135
146
 
136
147
  ```bash
137
- sudo systemctl --user status maxy-ttyd
138
- sudo tail -f ~/.maxy/logs/terminal.log
148
+ # The spawned binary must appear in this log with the bash-c wrapper
149
+ sudo grep 'started.*reason=upgrade' ~/.maxy/logs/terminal-launch.log | tail -3
150
+ # Expected shape: started pid=<N> display=:99 cmd="/usr/bin/xterm ... -e bash -c 'npx -y @rubytech/create-maxy@latest; exec bash'" transport=vnc windowPresent=true reason=upgrade
139
151
  ```
140
152
 
141
- In `terminal.log`, look for `terminal-proxy-flow` lines they're emitted every 5 seconds when bytes are moving and every ~30 seconds while idle, with `clientBytes`, `upstreamBytes`, and `idleMs` fields. A stuck upstream shows `upstreamBytes` frozen and `idleMs` climbing. If `maxy-ttyd` is not running, restart it with `sudo systemctl --user restart maxy-ttyd`, then close and reopen the Software Update window. If `ttyd` is healthy and `idleMs` keeps climbing, the installer process itself has died re-run `npx -y @rubytech/create-maxy@latest` from an SSH shell directly.
153
+ If the `cmd=` field does not contain `-e bash -c`, re-run the installer the vnc.sh on the device is pre-Task-643. If the command IS logged correctly but nothing is running, open the VNC overlay and type `history | tail` inside the shell if the npx line is there, it ran and exited (check `~/.maxy/logs/install-*.log` for the exit status).
142
154
 
143
- **If no narration appears at all within ~10 seconds of click:** the xterm renderer or its WebSocket is broken — reload the admin UI in your browser. The absence of the self-narration is itself the diagnostic. On reload, if the preamble line does not appear again within a few hundred milliseconds of clicking Upgrade, the admin server cannot reach `ttyd`; follow the "Admin terminal not available" steps above.
155
+ ---
144
156
 
145
157
  ## Header Terminal click shows an error alert
146
158
 
@@ -270,7 +282,7 @@ grep 'cmd=' ~/.maxy/logs/terminal-launch.log | tail -3
270
282
 
271
283
  If the failure log shows `window absent from target display`, the fix already ran but spawn still went to the wrong display — re-run the installer (`npx -y @rubytech/create-maxy@latest`) from a shell to pick up the latest `xterm` + `xdotool` preflight. If the failure log shows no recent entries and the iframe is still black, check `ss -ltn '( sport = 6080 or sport = 5900 )'` and `pgrep -af Xtigervnc|websockify` — the symptom might be a VNC-stack regression rather than a terminal-binary mismatch.
272
284
 
273
- The header Terminal is decoupled from `maxy-ttyd.service` by design stopping that service should *not* break the header Terminal. If the upgrade modal breaks but the header Terminal still works, the problem is isolated to ttyd (see the "Admin Terminal Stuck Disconnected After Upgrade" and "Admin terminal not available" sections above).
285
+ Both the header Terminal and the Software Update modal's Upgrade button now share the same VNC spawn pipeline, so a failure in one usually reproduces in the other — if the header Terminal opens a blank shell cleanly but the Upgrade modal errors, the problem is upgrade-specific (see "Software Update click shows an error" above).
274
286
 
275
287
  ## Orphan Account Directory Archived to `.trash/`
276
288
 
@@ -3,7 +3,8 @@
3
3
  # Called by systemd ExecStartPre (boot) and lib/vnc.ts ensureVnc() (recovery).
4
4
  #
5
5
  # Usage: vnc.sh start | stop | start-chrome | start-chrome-native
6
- # | start-terminal | start-terminal-native | status
6
+ # | start-terminal | start-terminal-native | start-terminal-upgrade
7
+ # | kill-terminal | status-terminal | status
7
8
  #
8
9
  # Components:
9
10
  # Xtigervnc :99 — virtual X11 display + VNC server on port 5900
@@ -430,6 +431,114 @@ start_terminal_native() {
430
431
  start_terminal_on "${NATIVE_DISPLAY}" "native"
431
432
  }
432
433
 
434
+ # Task 643 — upgrade-specialised terminal spawn. Sibling of start_terminal_on
435
+ # rather than a parameterised branch because two things diverge:
436
+ #
437
+ # (1) idempotency inverts. start_terminal_on short-circuits when a terminal
438
+ # is already alive on the target display; the upgrade variant MUST kill
439
+ # any pre-existing shell and spawn a fresh one so npx runs in a clean
440
+ # environment (no operator aliases, cwd, or half-typed commands).
441
+ #
442
+ # (2) binary invocation grows a command argument. Both binaries keep their
443
+ # existing flags (xterm: $XTERM_VNC_FLAGS, gnome-terminal: --wait), then
444
+ # append a binary-specific "run this command" dispatcher:
445
+ # xterm -e bash -c "<cmd>; exec bash"
446
+ # gnome-terminal -- bash -c "<cmd>; exec bash"
447
+ # The trailing `exec bash` replaces the shell process after the upgrade
448
+ # exits, so the terminal window stays open for scrollback review.
449
+ #
450
+ # Everything else — preflight, binary resolution, setsid -f detach,
451
+ # wait_for_terminal, check_window_on_display (Task 632 invariant) — is the
452
+ # same pipeline as start_terminal_on. The two functions share helpers, not
453
+ # bodies, so a future invariant addition (e.g. post-spawn keepalive) requires
454
+ # two edits, not one — an intentional tradeoff for readability.
455
+ start_terminal_upgrade_on() {
456
+ local target_display="$1"
457
+ local label="$2" # "vnc" | "native"
458
+
459
+ # Unconditional kill: no "already running" short-circuit. An upgrade grafted
460
+ # onto a pre-existing operator shell would inherit that shell's env, cwd,
461
+ # and interactive state — not acceptable for an installer run.
462
+ if terminal_alive; then
463
+ local existing_pid
464
+ existing_pid="$(terminal_pid)"
465
+ tlog "upgrade-kill pid=${existing_pid} reason=\"pre-upgrade fresh shell required\""
466
+ log "Terminal pid=${existing_pid} killed to spawn fresh upgrade shell (${label})"
467
+ kill_terminal_emulators
468
+ sleep 0.3
469
+ fi
470
+
471
+ local resolved bin flags
472
+ if ! resolved="$(resolve_terminal_bin "$target_display")"; then
473
+ echo "[terminal-launch] failed err=\"no terminal emulator installed\" transport=${label} reason=upgrade" >&2
474
+ return 1
475
+ fi
476
+ bin="${resolved%%$'\t'*}"
477
+ flags="${resolved#*$'\t'}"
478
+
479
+ # Binary-specific command dispatcher. Both variants wrap the upgrade in
480
+ # `bash -c "<cmd>; exec bash"` so the shell persists after npx exits.
481
+ local upgrade_cmd='npx -y @rubytech/create-maxy@latest'
482
+ local bash_wrapper="${upgrade_cmd}; exec bash"
483
+ local dispatch_flag
484
+ case "$bin" in
485
+ */xterm) dispatch_flag='-e' ;;
486
+ */gnome-terminal) dispatch_flag='--' ;;
487
+ *)
488
+ # resolve_terminal_bin only returns xterm or gnome-terminal paths. This
489
+ # branch is defensive; a future binary addition (e.g. kitty) would need
490
+ # its own dispatcher here.
491
+ tlog "failed err=\"unknown terminal binary dispatcher for ${bin}\" reason=upgrade"
492
+ echo "[terminal-launch] failed err=\"unknown terminal binary dispatcher for ${bin}\" transport=${label} reason=upgrade" >&2
493
+ return 1
494
+ ;;
495
+ esac
496
+
497
+ log "Starting ${bin} ${flags} ${dispatch_flag} bash -c \"${upgrade_cmd}; exec bash\" on ${target_display} (${label}) reason=upgrade"
498
+
499
+ # setsid -f detaches from this script's process group so the spawned terminal
500
+ # survives vnc.sh exiting. Output to TERMINAL_LOG so spawn-time stderr is
501
+ # captured. $flags stays unquoted for word-splitting (xterm's flags have
502
+ # multiple tokens); bash_wrapper stays quoted so the whole command string
503
+ # reaches bash -c as one argument.
504
+ if [ -n "$flags" ]; then
505
+ DISPLAY="${target_display}" setsid -f "$bin" $flags "$dispatch_flag" bash -c "$bash_wrapper" >> "$TERMINAL_LOG" 2>&1 || true
506
+ else
507
+ DISPLAY="${target_display}" setsid -f "$bin" "$dispatch_flag" bash -c "$bash_wrapper" >> "$TERMINAL_LOG" 2>&1 || true
508
+ fi
509
+
510
+ if ! wait_for_terminal; then
511
+ local diag="failed err=\"spawn detached but no terminal PID visible within 1s\" transport=${label} cmd=\"${bin} ${flags} ${dispatch_flag} bash -c '${upgrade_cmd}; exec bash'\" reason=upgrade"
512
+ tlog "$diag"
513
+ echo "[terminal-launch] $diag" >&2
514
+ log "ERROR: upgrade terminal failed to appear in pgrep within 1s on ${target_display} (${label})"
515
+ return 1
516
+ fi
517
+
518
+ local pid
519
+ pid="$(terminal_pid)"
520
+
521
+ # Task 632 invariant: PID presence is necessary but not sufficient.
522
+ if ! check_window_on_display "$target_display"; then
523
+ local observed
524
+ observed="$(_count_windows_on_display "$target_display")"
525
+ local diag="failed err=\"window absent from target display after spawn\" pid=${pid} display=${target_display} observed_windows=${observed} transport=${label} cmd=\"${bin} ${flags} ${dispatch_flag} bash -c '${upgrade_cmd}; exec bash'\" reason=upgrade"
526
+ tlog "$diag"
527
+ echo "[terminal-launch] $diag" >&2
528
+ log "ERROR: upgrade terminal pid=${pid} spawned but no window on ${target_display} (${label})"
529
+ return 1
530
+ fi
531
+
532
+ tlog "started pid=${pid} display=${target_display} cmd=\"${bin} ${flags} ${dispatch_flag} bash -c '${upgrade_cmd}; exec bash'\" transport=${label} windowPresent=true reason=upgrade"
533
+ log "Upgrade terminal ready (${label}) pid=${pid} cmd=\"${upgrade_cmd}\""
534
+ return 0
535
+ }
536
+
537
+ start_terminal_upgrade() {
538
+ preflight_terminal_deps || return 1
539
+ start_terminal_upgrade_on ":99" "vnc"
540
+ }
541
+
433
542
  start_chrome_native() {
434
543
  discover_native_session
435
544
 
@@ -556,6 +665,10 @@ case "${1:-}" in
556
665
  start_terminal_native
557
666
  ;;
558
667
 
668
+ start-terminal-upgrade)
669
+ start_terminal_upgrade
670
+ ;;
671
+
559
672
  kill-terminal)
560
673
  pid="$(terminal_pid)"
561
674
  kill_terminal_emulators
@@ -583,7 +696,7 @@ case "${1:-}" in
583
696
  ;;
584
697
 
585
698
  *)
586
- echo "Usage: vnc.sh start | stop | start-chrome | start-chrome-native | start-terminal | start-terminal-native | kill-terminal | status-terminal | status" >&2
699
+ echo "Usage: vnc.sh start | stop | start-chrome | start-chrome-native | start-terminal | start-terminal-native | start-terminal-upgrade | kill-terminal | status-terminal | status" >&2
587
700
  exit 1
588
701
  ;;
589
702
  esac