@runuai/host 0.9.140 → 0.9.141

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runuai/host",
3
- "version": "0.9.140",
3
+ "version": "0.9.141",
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>",
@@ -141,6 +141,42 @@ reject_workspace_runtime_shadow() {
141
141
  fi
142
142
  }
143
143
 
144
+ # The first check of a task-up used to be the refusal above. A fresh task
145
+ # never trips it (the workspace is empty), but a RESUMED one can: the
146
+ # workspace persists across an idle park, agents' shells start in /workspace,
147
+ # and `asdf local nodejs 22` from there writes exactly this file — after
148
+ # which the task could never wake again without someone deleting the file on
149
+ # the host (client runner, 2026-09-14). The entry is the task's own
150
+ # container-writable content, so move it aside instead: a rename is
151
+ # metadata-only (never opens a FIFO/device/socket), the runtime proof is
152
+ # unchanged because nothing is left at the root, and the content survives
153
+ # under .uai/refused/ where the agent can re-pin it in a project. The
154
+ # post-materialization recheck below keeps refusing — by then the root must
155
+ # be empty by construction, so an entry there is a bug, not a user file.
156
+ quarantine_workspace_runtime_shadow() {
157
+ local path="$task_workspace/.tool-versions" aside_dir aside
158
+ if [ -L "$path" ] || [ -e "$path" ]; then
159
+ aside_dir="$task_workspace/.uai/refused"
160
+ reject_unsafe_host_directory "$task_workspace/.uai" "the task control directory"
161
+ reject_unsafe_host_directory "$aside_dir" "the refused-files directory"
162
+ mkdir -p "$aside_dir"
163
+ aside="$aside_dir/tool-versions.$(date +%s)"
164
+ if ! mv -f "$path" "$aside" 2>/dev/null || [ -L "$path" ] || [ -e "$path" ]; then
165
+ reject_workspace_runtime_shadow
166
+ fi
167
+ cat > "$aside_dir/README.txt" <<'NOTE'
168
+ Uai moved a /workspace/.tool-versions aside before starting this task.
169
+
170
+ A .tool-versions at the workspace root is outside the runtime proof and would
171
+ override the runtimes proved for every project in the task, so the host refuses
172
+ to start a container while one exists. The file is kept here, untouched, under
173
+ its original content. Pin runtimes in a project's own .tool-versions instead
174
+ (/workspace/<project>/.tool-versions), which the materializer proves.
175
+ NOTE
176
+ log "moved a workspace-root .tool-versions aside to ${aside#"$task_workspace"/} (it would override the runtimes proved for this task; pin per project instead)"
177
+ fi
178
+ }
179
+
144
180
  mapped_host_port() {
145
181
  local container="$1" container_port="$2"
146
182
  docker port "$container" "$container_port" 2>/dev/null \
@@ -555,7 +591,7 @@ else
555
591
  fi
556
592
  reject_unsafe_host_directory "$task_workspace" "the task workspace"
557
593
  mkdir -p "$task_workspace"
558
- reject_workspace_runtime_shadow
594
+ quarantine_workspace_runtime_shadow
559
595
 
560
596
  # Git resolves symlinked ancestors when it writes a linked worktree's absolute
561
597
  # `.git` pointer. Keep the configured (lexical) task paths as Uai's stable host