@rubytech/create-realagent 1.0.678 → 1.0.681

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 (60) hide show
  1. package/dist/index.js +232 -39
  2. package/package.json +1 -1
  3. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.d.ts +2 -0
  4. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.d.ts.map +1 -0
  5. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.js +112 -0
  6. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.js.map +1 -0
  7. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.d.ts +2 -0
  8. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.d.ts.map +1 -0
  9. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.js +163 -0
  10. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.js.map +1 -0
  11. package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts +38 -0
  12. package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts.map +1 -0
  13. package/payload/platform/lib/graph-mcp/dist/cypher-validate.js +130 -0
  14. package/payload/platform/lib/graph-mcp/dist/cypher-validate.js.map +1 -0
  15. package/payload/platform/lib/graph-mcp/dist/index.js +201 -45
  16. package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -1
  17. package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts +78 -0
  18. package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts.map +1 -0
  19. package/payload/platform/lib/graph-mcp/dist/schema-cache.js +194 -0
  20. package/payload/platform/lib/graph-mcp/dist/schema-cache.js.map +1 -0
  21. package/payload/platform/lib/graph-mcp/src/__tests__/cypher-validate.test.ts +141 -0
  22. package/payload/platform/lib/graph-mcp/src/__tests__/schema-cache.test.ts +169 -0
  23. package/payload/platform/lib/graph-mcp/src/cypher-validate.ts +157 -0
  24. package/payload/platform/lib/graph-mcp/src/index.ts +247 -47
  25. package/payload/platform/lib/graph-mcp/src/schema-cache.ts +212 -0
  26. package/payload/platform/lib/graph-trash/dist/index.d.ts +8 -0
  27. package/payload/platform/lib/graph-trash/dist/index.d.ts.map +1 -1
  28. package/payload/platform/lib/graph-trash/dist/index.js +109 -14
  29. package/payload/platform/lib/graph-trash/dist/index.js.map +1 -1
  30. package/payload/platform/lib/graph-trash/src/index.ts +136 -21
  31. package/payload/platform/plugins/docs/references/deployment.md +4 -2
  32. package/payload/platform/plugins/docs/references/memory-guide.md +5 -1
  33. package/payload/platform/plugins/docs/references/platform.md +1 -1
  34. package/payload/platform/plugins/docs/references/troubleshooting.md +20 -0
  35. package/payload/platform/plugins/memory/PLUGIN.md +1 -0
  36. package/payload/platform/plugins/memory/mcp/dist/index.js +54 -6
  37. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  38. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts +36 -0
  39. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts.map +1 -0
  40. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js +86 -0
  41. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js.map +1 -0
  42. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts +23 -0
  43. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts.map +1 -1
  44. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +47 -1
  45. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -1
  46. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts +58 -0
  47. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts.map +1 -0
  48. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js +125 -0
  49. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js.map +1 -0
  50. package/payload/platform/scripts/vnc.sh +12 -409
  51. package/payload/platform/templates/agents/admin/IDENTITY.md +16 -0
  52. package/payload/platform/templates/dotfiles/.tmux.conf +1 -0
  53. package/payload/platform/templates/systemd/maxy-ttyd.service +25 -0
  54. package/payload/server/chunk-3RBKKDHC.js +783 -0
  55. package/payload/server/maxy-edge.js +377 -8
  56. package/payload/server/public/assets/admin-CIkyOur7.js +362 -0
  57. package/payload/server/public/assets/admin-kHJ-D0s7.css +1 -0
  58. package/payload/server/public/index.html +2 -1
  59. package/payload/server/server.js +391 -412
  60. package/payload/server/public/assets/admin-BBL1no_g.js +0 -352
@@ -2,19 +2,17 @@
2
2
  # VNC + browser lifecycle — single source of truth.
3
3
  # Called by systemd ExecStartPre (boot) and lib/vnc.ts ensureVnc() (recovery).
4
4
  #
5
- # Usage: vnc.sh start | stop | start-chrome | start-chrome-native
6
- # | start-terminal | start-terminal-native | start-terminal-upgrade
7
- # | kill-terminal | status-terminal | status
5
+ # Usage: vnc.sh start | stop | start-chrome | start-chrome-native | status
8
6
  #
9
7
  # Components:
10
8
  # Xtigervnc :99 — virtual X11 display + VNC server on port 5900
11
9
  # websockify :6080 — WebSocket bridge serving noVNC static files
12
10
  # Chromium :9222 — headed browser with CDP enabled
13
11
  # (Playwright MCP connects via --cdp-endpoint)
14
- # Terminal emulator — gnome-terminal or xterm, spawned on demand for the
15
- # admin Terminal overlay (Task 627). Isomorphic to the
16
- # Chromium pipeline same :99 VNC display, same
17
- # /vnc-viewer.html iframe surface.
12
+ #
13
+ # Task 657: the admin Terminal overlay and upgrade modal now use the
14
+ # byte-stream xterm.js surface over /ttyd on maxy-edge, not VNC. This
15
+ # script no longer spawns GUI terminal emulators.
18
16
  #
19
17
  # Display modes (DISPLAY_MODE env var, set by installer --display flag):
20
18
  # virtual (default) — Chromium runs on :99 (VNC virtual display)
@@ -39,19 +37,13 @@ fi
39
37
  MAXY_DIR="${HOME}/${CONFIG_DIR}"
40
38
  LOG_DIR="${MAXY_DIR}/logs"
41
39
  LOG_FILE="${LOG_DIR}/vnc-boot.log"
42
- TERMINAL_LOG="${LOG_DIR}/terminal-launch.log"
43
40
 
44
41
  mkdir -p "$LOG_DIR"
45
42
 
46
43
  log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"; }
47
- tlog() { echo "[$(date '+%Y-%m-%dT%H:%M:%S%z')] [terminal-launch] $*" >> "$TERMINAL_LOG"; }
48
44
 
49
45
  kill_stale() {
50
46
  pkill -f 'chromium.*remote-debugging-port=9222' 2>/dev/null || true
51
- # Terminal emulators launched by start-terminal[-native] (Task 627).
52
- # Regex is anchored to avoid killing gnome-terminal-server (D-Bus service
53
- # always running on GNOME desktops — not ours to manage).
54
- kill_terminal_emulators
55
47
  pkill -f 'Xtigervnc :99' 2>/dev/null || true
56
48
  pkill -f 'websockify.*6080' 2>/dev/null || true
57
49
  rm -f /tmp/.X99-lock /tmp/.X11-unix/X99
@@ -174,8 +166,8 @@ start_chrome_on() {
174
166
  if wait_for_port 9222; then
175
167
  # Log-only observability (Task 632): the display-membership invariant is
176
168
  # asserted but does NOT gate success here. Chromium does not D-Bus-delegate
177
- # like gnome-terminal does, so the failure mode is not currently reachable
178
- # — but making the invariant visible now means future backend swaps (e.g.
169
+ # its window creation, so the failure mode is not currently reachable — but
170
+ # making the invariant visible now means future backend swaps (e.g.
179
171
  # snap-installed Chromium on Ubuntu with different IPC) surface the drift
180
172
  # in vnc-boot.log rather than as a silent-black iframe.
181
173
  if check_window_on_display "${target_display}"; then
@@ -193,373 +185,12 @@ start_chrome() {
193
185
  }
194
186
 
195
187
  # ---------------------------------------------------------------------------
196
- # Terminal emulator lifecycle (Task 627) isomorphic to Chromium's.
188
+ # Task 657 retired the VNC-as-terminal path (Task 627/643): the header
189
+ # TerminalOverlay and UpdateModal now mount xterm.js against /ttyd on
190
+ # maxy-edge. The GUI-terminal spawn pipeline that lived here is gone.
191
+ # Only the Chromium surface remains for BrowserOverlay.
197
192
  # ---------------------------------------------------------------------------
198
193
 
199
- # Resolve the preferred terminal binary + its required flags. Selection depends
200
- # on the target display because gnome-terminal is a D-Bus launcher that routes
201
- # through /usr/libexec/gnome-terminal-server — and that server is bound to the
202
- # operator's login session bus (typically :0 on a GNOME desktop). Calling
203
- # `DISPLAY=:99 /usr/bin/gnome-terminal` does NOT open a window on :99; the
204
- # D-Bus request is delegated to the server on :0 and the window appears there
205
- # instead (Task 632 root cause). xterm is a standalone X client with no IPC
206
- # layer, so `DISPLAY=:99 xterm` lands on :99 regardless of session state.
207
- #
208
- # Selection rule:
209
- # target_display == :99 (VNC virtual) → xterm-first, gnome-terminal fallback
210
- # target_display == :0 (native login) → gnome-terminal-first, xterm fallback
211
- # (the session's gnome-terminal-server owns :0, so D-Bus delegation lands
212
- # on the right display here — no bug reachable on loopback)
213
- #
214
- # Prints "<bin>\t<flags>" on stdout (tab-separated), or exits non-zero with a
215
- # loud-fail log if neither is installed. gnome-terminal retains `--wait`
216
- # (see Task 627 pgrep-visibility fix). xterm takes no flag.
217
- # xterm flags for the :99 VNC framebuffer. Fontconfig alias `monospace`
218
- # resolves via /etc/fonts to the distro's default monospace face (DejaVu
219
- # Sans Mono on Ubuntu/Debian) — no quoting needed, so the flag string
220
- # survives unquoted $flags word-splitting in start_terminal_on. Geometry
221
- # 160x45 fills the Xtigervnc display (1280x800 — see Xtigervnc -geometry
222
- # flag below) at 11pt; the prior empty flag string left xterm at its 80x24
223
- # default with the 6x13 `fixed` bitmap font, which rendered ~480x312 px of
224
- # the 1280x800 framebuffer with a black void around it.
225
- XTERM_VNC_FLAGS='-fa monospace -fs 11 -geometry 160x45'
226
-
227
- resolve_terminal_bin() {
228
- local target_display="${1:-:99}"
229
- local xterm_bin='' gnome_bin=''
230
- [ -x /usr/bin/xterm ] && xterm_bin='/usr/bin/xterm'
231
- [ -x /usr/bin/gnome-terminal ] && gnome_bin='/usr/bin/gnome-terminal'
232
-
233
- if [ "$target_display" = ":99" ]; then
234
- [ -n "$xterm_bin" ] && { printf '%s\t%s\n' "$xterm_bin" "$XTERM_VNC_FLAGS"; return 0; }
235
- [ -n "$gnome_bin" ] && { printf '%s\t%s\n' "$gnome_bin" '--wait'; return 0; }
236
- else
237
- [ -n "$gnome_bin" ] && { printf '%s\t%s\n' "$gnome_bin" '--wait'; return 0; }
238
- [ -n "$xterm_bin" ] && { printf '%s\t%s\n' "$xterm_bin" "$XTERM_VNC_FLAGS"; return 0; }
239
- fi
240
- tlog "failed err=\"no terminal emulator installed (expected /usr/bin/gnome-terminal or /usr/bin/xterm)\""
241
- log "ERROR: no terminal emulator binary found — install xterm (apt-get install -y xterm)"
242
- return 1
243
- }
244
-
245
- # Task 634 — runtime preflight for terminal-launch dependencies. Separates
246
- # exit 127 (xdotool missing from PATH) from exit 1 (xdotool present but no
247
- # window mapped) in check_window_on_display. The installer now verifies
248
- # apt deps via `dpkg -s` but an operator can still `apt remove xdotool`
249
- # post-install, so this is the matching runtime guard. Emits the failure
250
- # string on stderr so ensureTerminal's extractFailureLine captures it
251
- # verbatim into server.log's `ensure-terminal err=...` field.
252
- preflight_terminal_deps() {
253
- if command -v xdotool >/dev/null 2>&1; then
254
- log "xdotool: $(command -v xdotool)"
255
- return 0
256
- fi
257
- tlog "failed err=\"xdotool not installed — re-run installer to repair\""
258
- log "ERROR: xdotool missing — start-terminal cannot assert display-membership (re-run: npx -y @rubytech/create-maxy@latest)"
259
- echo "[terminal-launch] failed err=\"xdotool not installed — re-run installer to repair\"" >&2
260
- return 1
261
- }
262
-
263
- # Verify that at least one visible window is mapped on $target_display. Closes
264
- # the "PID visible in pgrep but window is not on target" class of silent fail
265
- # (Task 632). Uses xdotool's exit code directly — no stdout parsing for
266
- # control flow (feedback_no_stdout_parsing_for_control_flow.md). `--class '.'`
267
- # matches WM_CLASS (static for the window's lifetime) instead of WM_NAME
268
- # (which can be empty in the microseconds between window-create and
269
- # title-set). Retries 3× at 0.3s to absorb the window-map race.
270
- #
271
- # Returns 0 if any visible window appears on the display within ~1s, 1 otherwise.
272
- check_window_on_display() {
273
- local target_display="$1"
274
- # Task 634 — route xdotool stderr to vnc-boot.log instead of /dev/null.
275
- # preflight_terminal_deps removes the 127-vs-1 conflation from production,
276
- # but if xdotool ever fails for a non-missing-binary reason (broken X
277
- # server, malformed DISPLAY), the diagnostic now lands in a grep-addressable
278
- # log instead of vanishing. Control flow still depends solely on the exit
279
- # code (feedback_no_stdout_parsing_for_control_flow.md).
280
- for _ in 1 2 3; do
281
- if DISPLAY="$target_display" xdotool search --onlyvisible --class '.' >/dev/null 2>>"$LOG_FILE"; then
282
- return 0
283
- fi
284
- sleep 0.3
285
- done
286
- return 1
287
- }
288
-
289
- # Count visible windows on $target_display (diagnostic; used in failure logs).
290
- # Prints the integer count. Returns 0 on empty display (not an error).
291
- _count_windows_on_display() {
292
- local target_display="$1"
293
- DISPLAY="$target_display" xdotool search --onlyvisible --class '.' 2>/dev/null | wc -l | tr -d ' '
294
- }
295
-
296
- # pgrep pattern that matches operator-launched terminals but NEVER matches
297
- # /usr/libexec/gnome-terminal-server (D-Bus service, pre-existing). Matches:
298
- # - /usr/bin/gnome-terminal --wait (Ubuntu's python wrapper, invoked with --wait)
299
- # - /usr/bin/python3 /usr/bin/gnome-terminal (wrapper as seen via pgrep -f)
300
- # - /usr/bin/gnome-terminal.real --wait (actual binary, child of wrapper)
301
- # - /usr/bin/xterm, xterm -geometry ... (xterm, single-process emulator)
302
- # Rejects:
303
- # - /usr/libexec/gnome-terminal-server (D-Bus service, not ours to manage)
304
- #
305
- # The `(^|/)` alternation anchors on either the start of the cmdline or a
306
- # preceding `/` path separator, so the python wrapper path `/usr/bin/python3
307
- # /usr/bin/gnome-terminal --wait` is matched via the inner `/gnome-terminal`.
308
- # The `(\.real)?` optional suffix catches the actual binary. The trailing
309
- # `([[:space:]]|$)` breaks the match on `-server` (dash is not whitespace).
310
- # POSIX ERE — procps `pgrep` does not support `\s`, so use [[:space:]].
311
- _terminal_pgrep_pattern() {
312
- echo '(^|/)(gnome-terminal(\.real)?([[:space:]]|$)|xterm([[:space:]]|$))'
313
- }
314
-
315
- # Return 0 if a launched terminal is alive, 1 otherwise.
316
- # ensureTerminal() uses this as its post-spawn liveness probe (the
317
- # terminal-domain analogue of waitForPort — terminals have no port).
318
- terminal_alive() {
319
- pgrep -f "$(_terminal_pgrep_pattern)" >/dev/null 2>&1
320
- }
321
-
322
- # Return the first matching PID (used in logs). Empty string if none alive.
323
- terminal_pid() {
324
- pgrep -f "$(_terminal_pgrep_pattern)" 2>/dev/null | head -n 1
325
- }
326
-
327
- # Kill all operator-launched terminal emulators. Pre-existing
328
- # gnome-terminal-server is unaffected by the anchored regex.
329
- kill_terminal_emulators() {
330
- pkill -f "$(_terminal_pgrep_pattern)" 2>/dev/null || true
331
- }
332
-
333
- # Wait up to 1s for the spawned terminal to show up in pgrep.
334
- # Mirrors wait_for_port's deadline semantics (3 × 0.3s = 0.9s wall-clock max).
335
- wait_for_terminal() {
336
- for _ in 1 2 3; do
337
- if terminal_alive; then
338
- return 0
339
- fi
340
- sleep 0.3
341
- done
342
- return 1
343
- }
344
-
345
- start_terminal_on() {
346
- local target_display="$1"
347
- local label="$2" # "vnc" | "native"
348
-
349
- # Idempotency: if a terminal is already alive, verify its window is actually
350
- # mapped on $target_display before accepting. The pgrep-based liveness probe
351
- # cannot distinguish a terminal on :0 (stale from an unclean close, or
352
- # D-Bus-delegated by a pre-Task-632 gnome-terminal call) from one on :99.
353
- # Self-heal: if the PID exists but no window appears on the target display,
354
- # kill the stale emulator and fall through to respawn.
355
- if terminal_alive; then
356
- local existing_pid
357
- existing_pid="$(terminal_pid)"
358
- if check_window_on_display "$target_display"; then
359
- tlog "already-running pid=${existing_pid} display=${target_display} windowPresent=true"
360
- log "Terminal already running pid=${existing_pid} (${label})"
361
- return 0
362
- fi
363
- local observed
364
- observed="$(_count_windows_on_display "$target_display")"
365
- tlog "self-heal pid=${existing_pid} display=${target_display} observed_windows=${observed} transport=${label} reason=\"window absent on target, respawning\""
366
- log "Terminal pid=${existing_pid} alive but no window on ${target_display} — self-healing"
367
- kill_terminal_emulators
368
- sleep 0.3
369
- fi
370
-
371
- local resolved bin flags
372
- if ! resolved="$(resolve_terminal_bin "$target_display")"; then
373
- # resolve_terminal_bin already echoed the diagnostic via tlog; mirror it
374
- # to stderr so the Node caller (ensureTerminal) captures the exact string.
375
- echo "[terminal-launch] failed err=\"no terminal emulator installed\" transport=${label}" >&2
376
- return 1
377
- fi
378
- bin="${resolved%%$'\t'*}"
379
- flags="${resolved#*$'\t'}"
380
-
381
- log "Starting ${bin} ${flags} on ${target_display} (${label})"
382
-
383
- # setsid -f detaches the process from our controlling terminal and this
384
- # script's process group, so the spawned terminal survives vnc.sh exiting.
385
- # Output redirected to the terminal-launch log (not /dev/null) so any
386
- # spawn-time stderr is captured. Flags is unquoted on purpose so an empty
387
- # value (xterm's case) does not produce a stray "" arg.
388
- if [ -n "$flags" ]; then
389
- DISPLAY="${target_display}" setsid -f "$bin" $flags >> "$TERMINAL_LOG" 2>&1 || true
390
- else
391
- DISPLAY="${target_display}" setsid -f "$bin" >> "$TERMINAL_LOG" 2>&1 || true
392
- fi
393
-
394
- if ! wait_for_terminal; then
395
- local diag="failed err=\"spawn detached but no terminal PID visible within 1s\" transport=${label} cmd=\"${bin} ${flags}\""
396
- tlog "$diag"
397
- echo "[terminal-launch] $diag" >&2
398
- log "ERROR: terminal failed to appear in pgrep within 1s on ${target_display} (${label}) — investigate setsid -f detachment / --wait flag"
399
- return 1
400
- fi
401
-
402
- local pid
403
- pid="$(terminal_pid)"
404
-
405
- # Post-spawn invariant (Task 632): PID presence is necessary but not
406
- # sufficient — assert the window actually landed on the target display.
407
- if ! check_window_on_display "$target_display"; then
408
- local observed
409
- observed="$(_count_windows_on_display "$target_display")"
410
- local diag="failed err=\"window absent from target display after spawn\" pid=${pid} display=${target_display} observed_windows=${observed} transport=${label} cmd=\"${bin} ${flags}\""
411
- tlog "$diag"
412
- echo "[terminal-launch] $diag" >&2
413
- log "ERROR: terminal pid=${pid} spawned but no window on ${target_display} (${label}) — likely D-Bus delegation to another display"
414
- return 1
415
- fi
416
-
417
- tlog "started pid=${pid} display=${target_display} cmd=\"${bin} ${flags}\" transport=${label} windowPresent=true"
418
- log "Terminal ready (${label}) pid=${pid}"
419
- return 0
420
- }
421
-
422
- start_terminal() {
423
- preflight_terminal_deps || return 1
424
- start_terminal_on ":99" "vnc"
425
- }
426
-
427
- start_terminal_native() {
428
- preflight_terminal_deps || return 1
429
- discover_native_session
430
- start_terminal_on "${NATIVE_DISPLAY}" "native"
431
- }
432
-
433
- # Task 643 — upgrade-specialised terminal spawn. Sibling of start_terminal_on
434
- # rather than a parameterised branch because two things diverge:
435
- #
436
- # (1) idempotency inverts. start_terminal_on short-circuits when a terminal
437
- # is already alive on the target display; the upgrade variant MUST kill
438
- # any pre-existing shell and spawn a fresh one so npx runs in a clean
439
- # environment (no operator aliases, cwd, or half-typed commands).
440
- #
441
- # (2) binary invocation grows a command argument. Both binaries keep their
442
- # existing flags (xterm: $XTERM_VNC_FLAGS, gnome-terminal: --wait), then
443
- # append a binary-specific "run this command" dispatcher:
444
- # xterm -e bash -c "<cmd>; exec bash"
445
- # gnome-terminal -- bash -c "<cmd>; exec bash"
446
- # The trailing `exec bash` replaces the shell process after the upgrade
447
- # exits, so the terminal window stays open for scrollback review.
448
- #
449
- # Everything else — preflight, binary resolution, setsid -f detach,
450
- # wait_for_terminal, check_window_on_display (Task 632 invariant) — is the
451
- # same pipeline as start_terminal_on. The two functions share helpers, not
452
- # bodies, so a future invariant addition (e.g. post-spawn keepalive) requires
453
- # two edits, not one — an intentional tradeoff for readability.
454
- start_terminal_upgrade_on() {
455
- local target_display="$1"
456
- local label="$2" # "vnc" | "native"
457
-
458
- # Unconditional kill: no "already running" short-circuit. An upgrade grafted
459
- # onto a pre-existing operator shell would inherit that shell's env, cwd,
460
- # and interactive state — not acceptable for an installer run.
461
- if terminal_alive; then
462
- local existing_pid
463
- existing_pid="$(terminal_pid)"
464
- tlog "upgrade-kill pid=${existing_pid} reason=\"pre-upgrade fresh shell required\""
465
- log "Terminal pid=${existing_pid} killed to spawn fresh upgrade shell (${label})"
466
- kill_terminal_emulators
467
- sleep 0.3
468
- fi
469
-
470
- local resolved bin flags
471
- if ! resolved="$(resolve_terminal_bin "$target_display")"; then
472
- echo "[terminal-launch] failed err=\"no terminal emulator installed\" transport=${label} reason=upgrade" >&2
473
- return 1
474
- fi
475
- bin="${resolved%%$'\t'*}"
476
- flags="${resolved#*$'\t'}"
477
-
478
- # Binary-specific command dispatcher. Both variants wrap the upgrade in
479
- # `bash -c "<cmd>; exec bash"` so the shell persists after npx exits.
480
- local upgrade_cmd='npx -y @rubytech/create-maxy@latest'
481
- local bash_wrapper="${upgrade_cmd}; exec bash"
482
- local dispatch_flag
483
- case "$bin" in
484
- */xterm) dispatch_flag='-e' ;;
485
- */gnome-terminal) dispatch_flag='--' ;;
486
- *)
487
- # resolve_terminal_bin only returns xterm or gnome-terminal paths. This
488
- # branch is defensive; a future binary addition (e.g. kitty) would need
489
- # its own dispatcher here.
490
- tlog "failed err=\"unknown terminal binary dispatcher for ${bin}\" reason=upgrade"
491
- echo "[terminal-launch] failed err=\"unknown terminal binary dispatcher for ${bin}\" transport=${label} reason=upgrade" >&2
492
- return 1
493
- ;;
494
- esac
495
-
496
- # Task 647: the upgrade command (`npx -y @rubytech/create-maxy@latest`) calls
497
- # `systemctl --user restart maxy-ui` partway through. If the terminal shell
498
- # lives in maxy-ui's cgroup it gets SIGKILL'd by that restart and the install
499
- # aborts. `systemd-run --user --scope` places the shell in its own transient
500
- # scope unit whose lifetime is independent of whichever service triggered
501
- # vnc.sh. `setsid -f` is no longer needed — scope units handle detachment.
502
- # Fallback (no systemd-run): `setsid -f`, with the known caveat that the
503
- # caller must not be inside a unit scheduled for restart during the install.
504
- local run_prefix=""
505
- if command -v systemd-run >/dev/null 2>&1; then
506
- run_prefix="systemd-run --user --quiet --scope --unit=maxy-upgrade-terminal-$$.scope --collect --"
507
- log "Wrapping upgrade terminal in systemd-run --user --scope (Task 647)"
508
- else
509
- log "WARNING: systemd-run not available — falling back to setsid -f (terminal may die on maxy-ui restart)"
510
- fi
511
- log "Starting ${run_prefix} ${bin} ${flags} ${dispatch_flag} bash -c \"${upgrade_cmd}; exec bash\" on ${target_display} (${label}) reason=upgrade"
512
-
513
- # $flags stays unquoted for word-splitting (xterm's flags have multiple
514
- # tokens); bash_wrapper stays quoted so the whole command string reaches
515
- # bash -c as one argument. $run_prefix is unquoted so its words expand.
516
- if [ -n "$run_prefix" ]; then
517
- if [ -n "$flags" ]; then
518
- DISPLAY="${target_display}" $run_prefix "$bin" $flags "$dispatch_flag" bash -c "$bash_wrapper" >> "$TERMINAL_LOG" 2>&1 &
519
- else
520
- DISPLAY="${target_display}" $run_prefix "$bin" "$dispatch_flag" bash -c "$bash_wrapper" >> "$TERMINAL_LOG" 2>&1 &
521
- fi
522
- disown 2>/dev/null || true
523
- else
524
- if [ -n "$flags" ]; then
525
- DISPLAY="${target_display}" setsid -f "$bin" $flags "$dispatch_flag" bash -c "$bash_wrapper" >> "$TERMINAL_LOG" 2>&1 || true
526
- else
527
- DISPLAY="${target_display}" setsid -f "$bin" "$dispatch_flag" bash -c "$bash_wrapper" >> "$TERMINAL_LOG" 2>&1 || true
528
- fi
529
- fi
530
-
531
- if ! wait_for_terminal; then
532
- 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"
533
- tlog "$diag"
534
- echo "[terminal-launch] $diag" >&2
535
- log "ERROR: upgrade terminal failed to appear in pgrep within 1s on ${target_display} (${label})"
536
- return 1
537
- fi
538
-
539
- local pid
540
- pid="$(terminal_pid)"
541
-
542
- # Task 632 invariant: PID presence is necessary but not sufficient.
543
- if ! check_window_on_display "$target_display"; then
544
- local observed
545
- observed="$(_count_windows_on_display "$target_display")"
546
- 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"
547
- tlog "$diag"
548
- echo "[terminal-launch] $diag" >&2
549
- log "ERROR: upgrade terminal pid=${pid} spawned but no window on ${target_display} (${label})"
550
- return 1
551
- fi
552
-
553
- tlog "started pid=${pid} display=${target_display} cmd=\"${bin} ${flags} ${dispatch_flag} bash -c '${upgrade_cmd}; exec bash'\" transport=${label} windowPresent=true reason=upgrade"
554
- log "Upgrade terminal ready (${label}) pid=${pid} cmd=\"${upgrade_cmd}\""
555
- return 0
556
- }
557
-
558
- start_terminal_upgrade() {
559
- preflight_terminal_deps || return 1
560
- start_terminal_upgrade_on ":99" "vnc"
561
- }
562
-
563
194
  start_chrome_native() {
564
195
  discover_native_session
565
196
 
@@ -678,34 +309,6 @@ case "${1:-}" in
678
309
  start_chrome_native
679
310
  ;;
680
311
 
681
- start-terminal)
682
- start_terminal
683
- ;;
684
-
685
- start-terminal-native)
686
- start_terminal_native
687
- ;;
688
-
689
- start-terminal-upgrade)
690
- start_terminal_upgrade
691
- ;;
692
-
693
- kill-terminal)
694
- pid="$(terminal_pid)"
695
- kill_terminal_emulators
696
- if [ -n "$pid" ]; then
697
- tlog "killed pid=${pid} reason=overlay-close"
698
- else
699
- tlog "killed-noop"
700
- fi
701
- ;;
702
-
703
- status-terminal)
704
- # Exit 0 if an operator-launched terminal is running, 1 otherwise.
705
- # Used by ensureTerminal() in vnc.ts as the in-process liveness probe.
706
- terminal_alive && exit 0 || exit 1
707
- ;;
708
-
709
312
  stop)
710
313
  log "Stopping VNC stack"
711
314
  kill_stale
@@ -717,7 +320,7 @@ case "${1:-}" in
717
320
  ;;
718
321
 
719
322
  *)
720
- 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
323
+ echo "Usage: vnc.sh start | stop | start-chrome | start-chrome-native | status" >&2
721
324
  exit 1
722
325
  ;;
723
326
  esac
@@ -68,6 +68,14 @@ When using WebSearch directly (not via the researcher specialist), the same disc
68
68
  - Your working directory is `$ACCOUNT_DIR` — your entire filesystem scope. Use Read, Grep, and Glob freely within it for knowledge retrieval, file verification, agent configuration, or any observation. Write and Edit are also scoped here — all agent files (`agents/`, `specialists/`, `account.json`) live in this directory. Never write to `$PLATFORM_ROOT/` or paths outside `$ACCOUNT_DIR`.
69
69
  - MCP tool schemas are deferred. Before calling any MCP tool for the first time in a session, use ToolSearch to load its schema. Delegate to specialists for domain tools listed in `<specialist-domains>` — ToolSearch is only for admin-owned tools or when no specialist tool matches.
70
70
 
71
+ ## Bulk-delete discipline
72
+
73
+ Never author delete-selection Cypher directly. When the owner asks to bulk-clean Conversations ("trash all empty conversations," "clean up single-assistant tests," "remove the primer-only public ones"), use `memory-find-candidates(filter=…)` to produce the candidate list and `memory-delete(elementId=…, filterToken=…)` to trash each one — the server re-runs the same predicate per elementId before the trash happens. A hand-rolled `MATCH … DELETE` or `MATCH … SET :Trashed` against user-domain nodes is a bug, because the admin agent does not hold the canonical schema in context and has already produced one schema-mismatch cascade (2026-04-22, 175 Conversations trashed via `[:HAS_MESSAGE]` where the real edge is `[:PART_OF]`). The filter-token path is the only path.
74
+
75
+ Never dispatch a subagent with a hand-built id list framed as "owner approved, don't reverify." The subagent has no way to verify the framing, and a bad list becomes irreversible cascade. If the work legitimately requires dispatch, the dispatched prompt must still pass each elementId through `memory-delete(filterToken=…)` so the server re-checks — no "trust me" bypass exists, and none should be invented.
76
+
77
+ Exception: single-node deletes where the owner points at a specific node from `memory-search`, a side-panel on `/graph`, or a message in chat do not need a filter token. The token mechanism exists for bulk selection, where the LLM is choosing from a predicate rather than a concrete node the owner named.
78
+
71
79
  ## Tool Failure Discipline
72
80
 
73
81
  When a tool returns an error, acknowledge the failure before taking any other action. Name what was attempted, what went wrong, and — if a `[tool-failure-diag]` line appears in `## Recent Tool Failures` — what the diagnostic reveals (DNS resolved? TCP connected? HTTP returned a status? the tool's internal pipeline timed out?). The diagnostic is there so the reader — owner or agent — does not have to guess.
@@ -76,6 +84,14 @@ Do not retry the same tool against the same target within a turn. A second ident
76
84
 
77
85
  When a tool returns a structured failure whose error content begins with an UPPERCASE_ERROR_CODE (for example `WEBFETCH_CANNOT_READ_JS_SPA`), the runtime has already determined that retrying the same tool will fail and that a substitute would launder uncertainty. Read the error's plain-English explanation, then write one or two sentences to the owner that name (a) what failed, (b) the reason in their language, and (c) the concrete actions they can take to unblock — typically pasting text or sending a screenshot. Do not silently dispatch a substitute (Playwright, research-assistant, memory-search) to continue the original instruction; that hides the failure and the owner loses the ability to judge whether the substitute's output answers their question. A verbal instruction in the current conversation is not consent — only an explicit standing policy recorded in account configuration counts, and no such mechanism exists today. Until one exists, every structured tool failure becomes a question for the owner. Wait for direction before resuming.
78
86
 
87
+ ## Cypher schema
88
+
89
+ Your system prompt contains a `# SCHEMA (Neo4j graph, canonical reference)` block listing every label and relationship type your graph actually contains. Before authoring any cypher against the memory graph, consult that block. Never invent an edge or label name that is not in it — the plausible-sounding names you half-remember from other systems (`HAS_MESSAGE`, `IN_CONVERSATION`, `CONTAINS_MESSAGE`) do not exist here; Messages attach to Conversations via `:PART_OF`, not any other edge.
90
+
91
+ The graph-mcp proxy validates every cypher call against the live Neo4j schema. Write cypher with an unknown label or edge is rejected before execution; read cypher with an unknown token is executed but a warnings block is prepended to the result so you see the problem before acting on the rows. A rejection arrives as an MCP tool error with a structured message naming the unknown token and the nearest known match — read it, correct the token, retry. Do not retry the same typo hoping for a different outcome, and do not invent a workaround cypher that avoids the rejected edge.
92
+
93
+ If the SCHEMA block looks stale against a fresh migration you know just landed, invoke `maxy-graph-get_neo4j_schema` to pull the live snapshot. That is the only sanctioned refresh path; the validator's own cache rebuilds within 60s anyway, so after a minute the rejection and the SCHEMA block are both current.
94
+
79
95
  ## Cloudflare operations
80
96
 
81
97
  When the operator's request touches Cloudflare — setup, diagnosis, reset, DNS edit, account switch, tunnel management — your permitted actions are exactly three:
@@ -0,0 +1 @@
1
+ set -g history-limit 50000
@@ -0,0 +1,25 @@
1
+ [Unit]
2
+ Description=Maxy admin terminal (ttyd + tmux) — persistent PTY for admin UI
3
+ After=default.target
4
+
5
+ [Service]
6
+ Type=simple
7
+ # ttyd binary lives at /usr/local/bin/ttyd — installed from pinned upstream
8
+ # GitHub releases (SHA256-verified) by create-maxy's step 11 (Task 602),
9
+ # because Debian Bookworm's apt does NOT carry a ttyd package. /usr/local/bin
10
+ # is the standard location for binaries installed outside the distro package
11
+ # manager; /usr/bin is reserved for apt-owned files.
12
+ # -p 7681 listen on 127.0.0.1:7681 (same-origin proxy in maxy-ui binds to it)
13
+ # -i 127.0.0.1 reject non-loopback connections at the ttyd layer as well
14
+ # -W writable (allow client → server input bytes)
15
+ # tmux new-session -A -s maxy-pty attach if session exists, create otherwise.
16
+ # Lifetime = user session / device lifetime. Outlives maxy-ui restarts because
17
+ # this unit has no After=maxy-ui.service and no Requires= — independent.
18
+ # -x 200 -y 50 initial geometry; xterm.js fit-addon drives runtime resizes.
19
+ ExecStart=/usr/local/bin/ttyd -p 7681 -i 127.0.0.1 -W tmux new-session -A -s maxy-pty -x 200 -y 50
20
+ Environment=TERM=xterm-256color
21
+ Restart=always
22
+ RestartSec=2
23
+
24
+ [Install]
25
+ WantedBy=default.target