@runuai/host 0.9.95 → 0.9.97

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.
@@ -933,7 +933,7 @@ console.log(
933
933
  * under tiny restart loops — x11vnc EXITS whenever the X server it watches
934
934
  * isn't up yet (the race that killed the first live run), and the loops
935
935
  * also reattach after the display owner changes. Safe to run repeatedly:
936
- * every piece is pgrep-guarded, including the loops themselves.
936
+ * every piece is guarded by the X lockfile or a pidfile, loops included.
937
937
  */
938
938
  /**
939
939
  * The viewer daemons, one docker-exec each (the single-line nohup shape is
@@ -950,6 +950,22 @@ console.log(
950
950
  * proxy only SOMETIMES and reconnects the rest (live 2026-09-02). So the
951
951
  * start branch — reached only when the tracked loop is already dead — first
952
952
  * reaps anything still holding the port, guaranteeing exactly one listener.
953
+ *
954
+ * That reap MUST be `pkill -A` (--ignore-ancestors). The step runs as
955
+ * `sh -c "<step>"`, so the launching shell's own cmdline carries the launch
956
+ * line and matches the reap pattern: plain `pkill -f` SIGTERMed that shell
957
+ * before it reached the nohup, and neither daemon ever started — host
958
+ * 0.9.94–0.9.96, every Linux runtime (the pgrep self-match above, wearing a
959
+ * kill; reproduced in the uai-linux VM: step exit 143, no pidfile). Both
960
+ * shipped images run procps-ng 4.0.2 (Debian bookworm), which has -A; an
961
+ * older procps rejects the flag with a usage error and reaps nothing, which
962
+ * merely restores the pre-reap behaviour and never the self-kill.
963
+ * `-A` protects the launching chain only: the apt worker below inlines
964
+ * these steps into `su -c` / `nohup sh -c` wrappers whose argv also
965
+ * matches, and a per-session reap that lands inside that worker's ~1 s
966
+ * viewer window can kill its node-uid shell. That only costs the worker
967
+ * its done-marker (set -e), so it re-runs on the next ensure and finds the
968
+ * pidfiles alive — self-healing, and no worse than the pre-reap world.
953
969
  */
954
970
  const VIEWER_STEPS: string[] = [
955
971
  // Xvfb — one-shot; the X lockfile is the truth (also set when an agent
@@ -958,9 +974,9 @@ const VIEWER_STEPS: string[] = [
958
974
  `[ -x /usr/bin/Xvfb ] || exit 0; [ -e ${X_LOCK} ] || /usr/bin/nohup /usr/bin/${XVFB_CMD} >>/tmp/uai-xvfb.log 2>&1 &`,
959
975
  // x11vnc under a restart loop (it exits whenever the X server isn't up
960
976
  // yet). Pidfile-guarded.
961
- `[ -x /usr/bin/x11vnc ] || exit 0; [ -f /tmp/uai-x11vnc.pid ] && /usr/bin/kill -0 "$(/usr/bin/cat /tmp/uai-x11vnc.pid)" 2>/dev/null && exit 0; /usr/bin/pkill -f 'x11vnc -display' 2>/dev/null; /usr/bin/nohup /bin/sh -c 'echo $$ > /tmp/uai-x11vnc.pid; while true; do /usr/bin/x11vnc -display ${SERVER_DISPLAY} -forever -shared -nopw -quiet >>/tmp/uai-x11vnc.log 2>&1; /bin/sleep 2; done' >/dev/null 2>&1 &`,
977
+ `[ -x /usr/bin/x11vnc ] || exit 0; [ -f /tmp/uai-x11vnc.pid ] && /usr/bin/kill -0 "$(/usr/bin/cat /tmp/uai-x11vnc.pid)" 2>/dev/null && exit 0; /usr/bin/pkill -A -f 'x11vnc -display' 2>/dev/null; /usr/bin/nohup /bin/sh -c 'echo $$ > /tmp/uai-x11vnc.pid; while true; do /usr/bin/x11vnc -display ${SERVER_DISPLAY} -forever -shared -nopw -quiet >>/tmp/uai-x11vnc.log 2>&1; /bin/sleep 2; done' >/dev/null 2>&1 &`,
962
978
  // websockify/noVNC under the same pattern.
963
- `[ -x /usr/bin/websockify ] || exit 0; [ -f /tmp/uai-novnc.pid ] && /usr/bin/kill -0 "$(/usr/bin/cat /tmp/uai-novnc.pid)" 2>/dev/null && exit 0; /usr/bin/pkill -f 'websockify.*6080' 2>/dev/null; /usr/bin/nohup /bin/sh -c 'echo $$ > /tmp/uai-novnc.pid; while true; do /usr/bin/websockify --web=/usr/share/novnc 0.0.0.0:6080 localhost:5900 >>/tmp/uai-websockify.log 2>&1; /bin/sleep 2; done' >/dev/null 2>&1 &`,
979
+ `[ -x /usr/bin/websockify ] || exit 0; [ -f /tmp/uai-novnc.pid ] && /usr/bin/kill -0 "$(/usr/bin/cat /tmp/uai-novnc.pid)" 2>/dev/null && exit 0; /usr/bin/pkill -A -f 'websockify.*6080' 2>/dev/null; /usr/bin/nohup /bin/sh -c 'echo $$ > /tmp/uai-novnc.pid; while true; do /usr/bin/websockify --web=/usr/share/novnc 0.0.0.0:6080 localhost:5900 >>/tmp/uai-websockify.log 2>&1; /bin/sleep 2; done' >/dev/null 2>&1 &`,
964
980
  ];
965
981
 
966
982
  const DEFAULT_NODE_BIN =
@@ -1085,13 +1101,20 @@ export async function setupBrowserTesting(
1085
1101
  // costs ~0.5s once the build is present.
1086
1102
  const installed = environment
1087
1103
  ? await (async () => {
1088
- await environment.exec({
1104
+ const chown = await environment.exec({
1089
1105
  argv: ["/usr/bin/chown", "node:node", "/opt/pw-browsers"],
1090
1106
  user: "root",
1091
1107
  env: authorityEnv,
1092
1108
  timeoutMs: 5_000,
1093
1109
  maxOutputBytes: 64 * 1024,
1094
1110
  });
1111
+ if (chown.exitCode !== 0) {
1112
+ // Same non-fatal stance as the CLI path below.
1113
+ console.warn(
1114
+ `[browser] task ${taskId}: chown /opt/pw-browsers failed (status ${chown.exitCode}); ` +
1115
+ `continuing with the prewarm. ${Buffer.from(chown.stderr).toString("utf8").slice(0, 200)}`,
1116
+ );
1117
+ }
1095
1118
  return environment.exec({
1096
1119
  argv: ["sh", "-lc", PREWARM_CMD],
1097
1120
  cwd: "/workspace",
@@ -1113,7 +1136,16 @@ export async function setupBrowserTesting(
1113
1136
  ],
1114
1137
  { timeoutMs: 5_000 },
1115
1138
  ).then(async (chown) => {
1116
- if (chown.status !== 0) return chown;
1139
+ if (chown.status !== 0) {
1140
+ // Not fatal. On apple-container /opt/pw-browsers is a host-wide
1141
+ // virtiofs volume whose ownership the guest cannot change (EPERM)
1142
+ // although it is already node-owned inside the container; the
1143
+ // prewarm below is the real test of writability, so let it decide.
1144
+ console.warn(
1145
+ `[browser] task ${taskId}: chown /opt/pw-browsers failed (status ${chown.status}); ` +
1146
+ `continuing with the prewarm. ${chown.stderr.slice(0, 200)}`,
1147
+ );
1148
+ }
1117
1149
  return taskContainerCli(
1118
1150
  [
1119
1151
  "exec",
@@ -3695,6 +3695,31 @@ export function buildSystemPreamble(
3695
3695
  "someone only to bring them in or hand work off. (2) You may occasionally",
3696
3696
  "receive a `[channel check-in]` asking you to catch up on the channel.",
3697
3697
  ];
3698
+ // Reply cards: agents may offer the human a bounded, clickable answer. Only
3699
+ // the human-facing agents get this (the crew talks to the Secretary, not
3700
+ // people). Engine-agnostic — it is a chat markdown convention.
3701
+ const replyCardBrief =
3702
+ isSecretaryCrew
3703
+ ? []
3704
+ : [
3705
+ "When you need a SHORT, bounded answer from the human — pick one of a",
3706
+ "few options, or a yes/no — you may offer a reply card instead of a",
3707
+ "plain question. Write a fenced block the app turns into buttons:",
3708
+ "",
3709
+ "```uai-ask",
3710
+ "type: choice",
3711
+ "question: Which chain should I target first?",
3712
+ "options:",
3713
+ " - Robinhood",
3714
+ " - Base",
3715
+ " - Skip for now",
3716
+ "```",
3717
+ "",
3718
+ "Use `type: confirm` (no options) for a yes/no. The human's click",
3719
+ "arrives as their normal reply. Offer a card only when the answer",
3720
+ "really is a small fixed set — for anything open-ended, just ask in",
3721
+ "prose. At most one card per message, and never for the crew.",
3722
+ ];
3698
3723
  const comms = [
3699
3724
  "## uai task channel",
3700
3725
  "",
@@ -3718,6 +3743,7 @@ export function buildSystemPreamble(
3718
3743
  ...(sessionInboxBrief.length > 0 ? [""] : []),
3719
3744
  ...secretaryRoleBrief,
3720
3745
  ...channelConventionsBrief,
3746
+ ...(replyCardBrief.length > 0 ? ["", ...replyCardBrief] : []),
3721
3747
  ...checkInTranscriptBrief,
3722
3748
  "",
3723
3749
  ...handoffBrief,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runuai/host",
3
- "version": "0.9.95",
3
+ "version": "0.9.97",
4
4
  "description": "Uai host — runs ephemeral AI tasks in containers on a machine you control.",
5
5
  "license": "MIT",
6
6
  "author": "Uai Tech <team@runuai.com>",