@runuai/host 0.9.13 → 0.9.42

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 (97) hide show
  1. package/README.md +22 -5
  2. package/db/migrations/0014_host_inventory_event_index.sql +1 -0
  3. package/db/migrations/0015_host_settings.sql +9 -0
  4. package/db/migrations/0016_task_environment.sql +2 -0
  5. package/db/migrations/meta/_journal.json +21 -0
  6. package/db/schema.ts +80 -30
  7. package/images/standard/Dockerfile +36 -10
  8. package/images/standard/README.md +63 -18
  9. package/images/standard/container/corepack-version +1 -0
  10. package/images/standard/container/uai-init +308 -38
  11. package/images/standard/container/uai-materialize-runtimes +1527 -0
  12. package/lib/agent-cli.ts +69 -4
  13. package/lib/agent.ts +46 -7
  14. package/lib/agents/claude.ts +13 -8
  15. package/lib/agents/codex.ts +11 -6
  16. package/lib/agents/cursor.ts +39 -29
  17. package/lib/agents/durable-proc.ts +20 -27
  18. package/lib/agents/factory.ts +9 -25
  19. package/lib/agents/grok.ts +43 -30
  20. package/lib/agents/kimi.ts +44 -29
  21. package/lib/agents/opencode.ts +43 -31
  22. package/lib/agents/proc.ts +149 -114
  23. package/lib/agents/transport.ts +62 -50
  24. package/lib/agents/types.ts +6 -4
  25. package/lib/apple-runtime-recycle.ts +236 -0
  26. package/lib/apple-uninstall-teardown.ts +224 -0
  27. package/lib/browser-testing.ts +233 -93
  28. package/lib/codex-auth.ts +40 -6
  29. package/lib/command-db.ts +20 -0
  30. package/lib/container-runtime.ts +1338 -0
  31. package/lib/db.ts +1 -0
  32. package/lib/docker-exec.ts +87 -5
  33. package/lib/engine-accounts.ts +68 -5
  34. package/lib/engine-login.ts +1952 -0
  35. package/lib/enrollment-state.ts +251 -0
  36. package/lib/env-file.ts +155 -0
  37. package/lib/env.ts +4 -0
  38. package/lib/git-diff.ts +98 -32
  39. package/lib/git-identity.ts +199 -87
  40. package/lib/github-tokens.ts +202 -91
  41. package/lib/host-cloud-url.ts +62 -0
  42. package/lib/host-config.ts +279 -0
  43. package/lib/host-logs.ts +962 -0
  44. package/lib/keyed-promise-tail.ts +23 -0
  45. package/lib/legacy-runtime-v1.fixture.ts +627 -0
  46. package/lib/managed-activation-watcher.ts +72 -0
  47. package/lib/managed-install-owner-watcher.ts +55 -0
  48. package/lib/managed-operation-drain.ts +49 -0
  49. package/lib/managed-runtime.ts +3644 -0
  50. package/lib/managed-update-scheduler.ts +125 -0
  51. package/lib/mcp-gateway.ts +450 -23
  52. package/lib/orchestrator.ts +3070 -223
  53. package/lib/preview-sidecar.ts +57 -13
  54. package/lib/release-manifest.ts +708 -0
  55. package/lib/release-trust.ts +28 -0
  56. package/lib/runtime-activation-tail.ts +232 -0
  57. package/lib/runtime-archive.ts +1086 -0
  58. package/lib/runtime-authority.ts +79 -0
  59. package/lib/runtime-guard.ts +36 -0
  60. package/lib/runtime-provider-state.ts +169 -0
  61. package/lib/runtime-state.ts +232 -12
  62. package/lib/skills.ts +24 -3
  63. package/lib/ssh.ts +18 -0
  64. package/lib/standard-image.ts +1104 -141
  65. package/lib/stopped-task-status-queue.ts +44 -0
  66. package/lib/task-container-cli.ts +269 -0
  67. package/lib/task-diff.ts +66 -46
  68. package/lib/task-environment/apple-container.ts +757 -0
  69. package/lib/task-environment/docker.ts +945 -0
  70. package/lib/task-environment/index.ts +364 -0
  71. package/lib/task-environment/legacy-adoption.ts +443 -0
  72. package/lib/task-environment/registry.ts +58 -0
  73. package/lib/task-environment/types.ts +408 -0
  74. package/lib/task-identity.ts +19 -0
  75. package/lib/task-inventory.ts +585 -0
  76. package/lib/tunnel-registry.ts +135 -19
  77. package/lib/tunnel-runtime.ts +235 -0
  78. package/package.json +1 -1
  79. package/scripts/agent/_common.sh +123 -3
  80. package/scripts/agent/task-down.sh +146 -38
  81. package/scripts/agent/task-status.sh +19 -3
  82. package/scripts/agent/task-up.sh +1463 -109
  83. package/scripts/install/darwin.ts +848 -50
  84. package/scripts/install/linux.ts +838 -35
  85. package/scripts/install/types.ts +43 -0
  86. package/scripts/install/util.ts +215 -8
  87. package/scripts/install/win.ts +12 -0
  88. package/src/apple-tunnel-route.ts +104 -0
  89. package/src/cli.ts +1464 -72
  90. package/src/event-outbox.ts +83 -4
  91. package/src/index.ts +871 -50
  92. package/src/main.ts +1398 -255
  93. package/src/paths.ts +17 -1
  94. package/src/protocol.ts +695 -1
  95. package/src/runtime-bootstrap.ts +165 -0
  96. package/src/ui/server.ts +46 -10
  97. package/src/ui/types.ts +37 -0
@@ -14,8 +14,15 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
14
  [ "$#" -eq 1 ] || emit_err "BAD_ARGS" "usage: task-down <taskId>" "args"
15
15
 
16
16
  task_id="$1"
17
+ # Make the ASCII security grammar independent of the operator's collation.
18
+ export LC_ALL=C
19
+ if [ "${#task_id}" -gt 128 ] ||
20
+ [[ ! "$task_id" =~ ^[a-z0-9][a-z0-9_-]*$ ]]; then
21
+ emit_err "BAD_ARGS" "invalid task id" "task identity validation"
22
+ fi
17
23
  UAI_TASK_ID="$task_id"
18
24
  setup_err_trap
25
+ validate_task_runtime_selection
19
26
 
20
27
  step "TASK_NOT_FOUND" "read task row"
21
28
  # Teardown must never depend on bookkeeping: a broken/missing command
@@ -26,51 +33,150 @@ task_json=$(db_get_task "$task_id" 2>/dev/null) || task_json="[]"
26
33
  row_count=$(jq 'length' <<<"$task_json" 2>/dev/null) || row_count=0
27
34
  if [ "$row_count" -lt 1 ]; then
28
35
  log "task row unavailable — tearing down by derived names"
29
- worktree_path=""
30
- compose_project="$(compose_project_for_task "$task_id")"
31
36
  else
32
- worktree_path=$(jq -r '.[0].worktree_path // ""' <<<"$task_json")
33
- compose_project=$(jq -r '.[0].compose_project // ""' <<<"$task_json")
34
- fi
35
- # A task-up failure can leave an error row with compose_project=NULL while its
36
- # conventionally named container still exists. Bookkeeping must never suppress
37
- # teardown; the compose project is deterministic from the task id.
38
- [ -n "$compose_project" ] || compose_project=$(compose_project_for_task "$task_id")
39
-
40
- # task_dir falls back to the derived path when the row's worktree_path is
41
- # missing (e.g. crashed before persisting).
42
- task_dir="$worktree_path"
43
- if [ -z "$task_dir" ]; then
44
- task_dir="$UAI_WORKSPACE_ROOT/tasks/$task_id"
37
+ UAI_RETRY_STATUS=$(jq -r '.[0].status // ""' <<<"$task_json")
45
38
  fi
39
+ # Persisted runtime paths are recovery hints, never deletion authority. A
40
+ # corrupt row must not aim compose/rm outside this exact task identity.
41
+ compose_project=$(compose_project_for_task "$task_id")
42
+ task_dir="$UAI_WORKSPACE_ROOT/tasks/$task_id"
46
43
 
47
44
  # -----------------------------------------------------------------------------
48
45
  # 1. Compose stack.
49
46
  # -----------------------------------------------------------------------------
50
47
 
51
- step "COMPOSE_DOWN_FAILED" "docker compose down -v --rmi local"
52
- if [ -n "$task_dir" ] && [ -f "$task_dir/.uai/docker-compose.yml" ]; then
53
- docker compose -p "$compose_project" -f "$task_dir/.uai/docker-compose.yml" \
54
- down -v --rmi local --remove-orphans >/dev/null 2>&1 || true
48
+ if [ "$TASK_RUNTIME_MODE" = "apple-container" ]; then
49
+ step "COMPOSE_DOWN_FAILED" "remove task container"
50
+ app_container=$(apple_app_container_for_task "$task_id")
51
+ derived_image="uai-task-${task_id}"
52
+ # Ownership proof before destruction (review 2026-08-18 rounds 5+6): every
53
+ # container Uai creates carries com.uai.task=<id>. Destruction requires the
54
+ # inspect response to prove EXACT identity — exactly one record, whose
55
+ # top-level id is the requested name, carrying the exact label — the same
56
+ # response-confusion discipline recovery applies. A container merely
57
+ # OCCUPYING the reserved name, or a confused/multi-record answer, is not
58
+ # authority to delete anything.
59
+ if existing_json=$(crt inspect "$app_container" 2>/dev/null); then
60
+ existing_ok=$(printf '%s' "$existing_json" \
61
+ | jq -r --arg name "$app_container" --arg tid "$task_id" '
62
+ if (type == "array" and length == 1
63
+ and (.[0] | type == "object")
64
+ and .[0].id == $name
65
+ and ((.[0].configuration.labels["com.uai.task"] // "") == $tid))
66
+ then "ok" else "mismatch" end' 2>/dev/null) \
67
+ || existing_ok="mismatch"
68
+ if [ "$existing_ok" != "ok" ]; then
69
+ emit_err "COMPOSE_DOWN_FAILED" \
70
+ "A container named $app_container exists but could not be proven as this task's own (exact identity + ownership label); Uai will not delete a container it cannot prove it owns. Remove or rename it, then retry." \
71
+ "remove task container"
72
+ fi
73
+ crt stop --time 10 "$app_container" >/dev/null 2>&1 || true
74
+ crt delete --force "$app_container" >/dev/null 2>&1 || true
75
+ fi
76
+ # A derived image is task-private; removing it mirrors `down --rmi local`.
77
+ crt image delete "$derived_image" >/dev/null 2>&1 || true
78
+ # Destruction is not best-effort: prove the container is gone before the
79
+ # writable Git data below is deleted or success is reported. A CLI failure
80
+ # that does not name this container as not-found proves nothing — the
81
+ # workspace and its Git data must survive until absence is confirmed. The
82
+ # Apple runtime has no per-task networks, sidecars, or anonymous volumes.
83
+ if crt inspect "$app_container" >/dev/null 2>&1; then
84
+ emit_err "COMPOSE_DOWN_FAILED" \
85
+ "task runtime resources remain after teardown" \
86
+ "container verification after cleanup"
87
+ fi
88
+ if ! apple_container_proven_absent "$app_container"; then
89
+ emit_err "COMPOSE_DOWN_FAILED" \
90
+ "could not verify task container removal; refusing to delete the task workspace" \
91
+ "container verification after cleanup"
92
+ fi
55
93
  else
56
- # Fallback: tear down by project name only.
57
- docker compose -p "$compose_project" down -v --rmi local --remove-orphans >/dev/null 2>&1 || true
58
- fi
59
- # `compose down` is intentionally best-effort because an already-absent
60
- # stack can make a config-less fallback fail. Destruction itself is not
61
- # best-effort: before deleting the worktree or reporting success, prove that
62
- # no container remains under the stable compose project name.
63
- remaining_containers=""
64
- if ! remaining_containers=$(docker ps -aq \
65
- --filter "label=com.docker.compose.project=$compose_project"); then
66
- emit_err "COMPOSE_DOWN_FAILED" \
67
- "could not verify teardown for compose project $compose_project" \
68
- "docker ps after compose down"
69
- fi
70
- if [ -n "$remaining_containers" ]; then
71
- emit_err "COMPOSE_DOWN_FAILED" \
72
- "compose project $compose_project still has containers after teardown" \
73
- "docker ps after compose down"
94
+ step "COMPOSE_DOWN_FAILED" "docker compose down -v --rmi local"
95
+ if [ ! -L "$task_dir" ] && [ -f "$task_dir/.uai/docker-compose.yml" ]; then
96
+ docker compose -p "$compose_project" -f "$task_dir/.uai/docker-compose.yml" \
97
+ down -v --rmi local --remove-orphans >/dev/null 2>&1 || true
98
+ else
99
+ # Fallback: tear down by project name only.
100
+ docker compose -p "$compose_project" down -v --rmi local --remove-orphans >/dev/null 2>&1 || true
101
+ fi
102
+ # `compose down` is intentionally best-effort because an already-absent
103
+ # stack can make a config-less fallback fail. Destruction itself is not
104
+ # best-effort: before deleting the worktree or reporting success, prove that
105
+ # no task-owned container/network/volume remains under exact labels.
106
+ preview_containers=""
107
+ if ! preview_containers=$(docker ps -aq \
108
+ --filter "label=com.runuai.preview-sidecar.task=$task_id"); then
109
+ emit_err "COMPOSE_DOWN_FAILED" \
110
+ "could not verify preview teardown for task $task_id" \
111
+ "docker ps after preview teardown"
112
+ fi
113
+ if [ -n "$preview_containers" ]; then
114
+ # Docker ids contain no whitespace; intentional splitting passes each exact
115
+ # id as its own argv entry.
116
+ # shellcheck disable=SC2086
117
+ docker rm -f $preview_containers >/dev/null 2>&1 || \
118
+ emit_err "COMPOSE_DOWN_FAILED" "could not remove task preview sidecars" "docker rm previews"
119
+ fi
120
+
121
+ remaining_containers=""
122
+ if ! remaining_containers=$(docker ps -aq \
123
+ --filter "label=com.docker.compose.project=$compose_project"); then
124
+ emit_err "COMPOSE_DOWN_FAILED" \
125
+ "could not verify teardown for compose project $compose_project" \
126
+ "docker ps after compose down"
127
+ fi
128
+ if [ -n "$remaining_containers" ]; then
129
+ # shellcheck disable=SC2086
130
+ docker rm -f $remaining_containers >/dev/null 2>&1 || \
131
+ emit_err "COMPOSE_DOWN_FAILED" "could not remove compose containers" "docker rm after compose down"
132
+ fi
133
+
134
+ remaining_networks=""
135
+ if ! remaining_networks=$(docker network ls -q \
136
+ --filter "label=com.docker.compose.project=$compose_project"); then
137
+ emit_err "COMPOSE_DOWN_FAILED" \
138
+ "could not verify compose networks for $compose_project" \
139
+ "docker network ls after compose down"
140
+ fi
141
+ if [ -n "$remaining_networks" ]; then
142
+ # shellcheck disable=SC2086
143
+ docker network rm $remaining_networks >/dev/null 2>&1 || \
144
+ emit_err "COMPOSE_DOWN_FAILED" "could not remove compose networks" "docker network rm"
145
+ fi
146
+
147
+ remaining_volumes=""
148
+ if ! remaining_volumes=$(docker volume ls -q \
149
+ --filter "label=com.docker.compose.project=$compose_project"); then
150
+ emit_err "COMPOSE_DOWN_FAILED" \
151
+ "could not verify compose volumes for $compose_project" \
152
+ "docker volume ls after compose down"
153
+ fi
154
+ if [ -n "$remaining_volumes" ]; then
155
+ # shellcheck disable=SC2086
156
+ docker volume rm $remaining_volumes >/dev/null 2>&1 || \
157
+ emit_err "COMPOSE_DOWN_FAILED" "could not remove compose volumes" "docker volume rm"
158
+ fi
159
+
160
+ # Re-query every exact label. A successful remove command is not itself proof,
161
+ # and a failed query is unknown rather than empty.
162
+ verified_previews=""
163
+ verified_containers=""
164
+ verified_networks=""
165
+ verified_volumes=""
166
+ if ! verified_previews=$(docker ps -aq --filter "label=com.runuai.preview-sidecar.task=$task_id") ||
167
+ ! verified_containers=$(docker ps -aq --filter "label=com.docker.compose.project=$compose_project") ||
168
+ ! verified_networks=$(docker network ls -q --filter "label=com.docker.compose.project=$compose_project") ||
169
+ ! verified_volumes=$(docker volume ls -q --filter "label=com.docker.compose.project=$compose_project"); then
170
+ emit_err "COMPOSE_DOWN_FAILED" \
171
+ "could not verify task runtime cleanup" \
172
+ "docker label verification after cleanup"
173
+ fi
174
+ if [ -n "$verified_previews" ] || [ -n "$verified_containers" ] ||
175
+ [ -n "$verified_networks" ] || [ -n "$verified_volumes" ]; then
176
+ emit_err "COMPOSE_DOWN_FAILED" \
177
+ "task runtime resources remain after teardown" \
178
+ "docker label verification after cleanup"
179
+ fi
74
180
  fi
75
181
 
76
182
  # -----------------------------------------------------------------------------
@@ -80,10 +186,12 @@ fi
80
186
  # that retired `repo.git` path is never trusted by task-up again.
81
187
  # -----------------------------------------------------------------------------
82
188
 
83
- if [ -n "$task_dir" ] && [ -d "$task_dir" ]; then
189
+ if [ -e "$task_dir" ] || [ -L "$task_dir" ]; then
84
190
  step "WORKTREE_REMOVE_FAILED" "rm -rf task dir"
85
191
  rm -rf "$task_dir"
86
192
  fi
193
+ [ ! -e "$task_dir" ] && [ ! -L "$task_dir" ] || \
194
+ emit_err "WORKTREE_REMOVE_FAILED" "task directory remains after teardown" "task dir verification"
87
195
 
88
196
  # -----------------------------------------------------------------------------
89
197
  # 3. Persist final state.
@@ -17,6 +17,8 @@ UAI_TASK_ID="$task_id"
17
17
  setup_err_trap
18
18
 
19
19
  step "TASK_NOT_FOUND" "read task row"
20
+ validate_task_runtime_selection
21
+
20
22
  task_json=$(db_get_task "$task_id")
21
23
  [ "$(jq 'length' <<<"$task_json")" -ge 1 ] \
22
24
  || emit_err "TASK_NOT_FOUND" "no such task: $task_id" "read task row"
@@ -24,11 +26,25 @@ task_json=$(db_get_task "$task_id")
24
26
  compose_project=$(jq -r '.[0].compose_project // ""' <<<"$task_json")
25
27
  worktree_path=$(jq -r '.[0].worktree_path // ""' <<<"$task_json")
26
28
 
27
- # Containers running under this compose project, JSON array of names.
29
+ # Containers running under this task, JSON array of names.
28
30
  step "DOCKER_PS_FAILED" "docker ps"
29
31
  containers_json="[]"
30
- if [ -n "$compose_project" ]; then
31
- raw=$(docker ps --filter "label=com.docker.compose.project=$compose_project" --format '{{.Names}}' || true)
32
+ if [ "$TASK_RUNTIME_MODE" = "apple-container" ]; then
33
+ # Absence (inspect exit 1) is a definitive "not running" answer, never an
34
+ # unknown-state error; only a live "running" state lists the container.
35
+ apple_app=$(apple_app_container_for_task "$task_id")
36
+ apple_state=$(crt inspect "$apple_app" 2>/dev/null \
37
+ | jq -r '.[0].status.state // empty' 2>/dev/null) || apple_state=""
38
+ if [ "$apple_state" = "running" ]; then
39
+ containers_json=$(jq -nc --arg n "$apple_app" '[$n]')
40
+ fi
41
+ elif [ -n "$compose_project" ]; then
42
+ if ! raw=$(docker ps --filter "label=com.docker.compose.project=$compose_project" --format '{{.Names}}'); then
43
+ jq -nc \
44
+ --arg m "docker ps did not answer; runtime state is unknown" \
45
+ '{ok:false,error:{code:"DOCKER_PS_FAILED",message:$m,step:"docker ps"}}' >&2
46
+ exit 1
47
+ fi
32
48
  if [ -n "$raw" ]; then
33
49
  containers_json=$(printf '%s\n' "$raw" | jq -R . | jq -sc .)
34
50
  fi