@runuai/host 0.8.19 → 0.8.21

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/lib/agent.ts CHANGED
@@ -75,6 +75,8 @@ const TaskUpData = z.object({
75
75
  worktreePath: z.string(),
76
76
  codeServerPort: z.number().int().positive().optional(),
77
77
  previewPorts: PreviewPortRuntimesSchema.optional(),
78
+ /** Degraded start: uai-init failed twice; surfaced as a feed system note. */
79
+ initWarning: z.string().optional(),
78
80
  });
79
81
  export type TaskUpResult = z.infer<typeof TaskUpData>;
80
82
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runuai/host",
3
- "version": "0.8.19",
3
+ "version": "0.8.21",
4
4
  "description": "Uai host — runs ephemeral AI coding tasks in Docker on a machine you control.",
5
5
  "license": "MIT",
6
6
  "author": "Diogo Perillo <diogo.perillo@gmail.com>",
@@ -20,17 +20,23 @@ UAI_TASK_ID="$task_id"
20
20
  setup_err_trap
21
21
 
22
22
  step "TASK_NOT_FOUND" "read task row"
23
- task_json=$(db_get_task "$task_id")
24
- # Idempotent: it's fine if the task row is gone emit success and return.
25
- if [ "$(jq 'length' <<<"$task_json")" -lt 1 ]; then
26
- emit_ok '{"alreadyGone":true}'
27
- exit 0
23
+ # Teardown must never depend on bookkeeping: a broken/missing command
24
+ # snapshot (seen live 2026-07-21 cleaning up after a failed task-up
25
+ # db_get_task errored, the trap reported TASK_NOT_FOUND, and the container
26
+ # survived) still tears the stack down by convention-derived names.
27
+ task_json=$(db_get_task "$task_id" 2>/dev/null) || task_json="[]"
28
+ row_count=$(jq 'length' <<<"$task_json" 2>/dev/null) || row_count=0
29
+ if [ "$row_count" -lt 1 ]; then
30
+ log "task row unavailable — tearing down by derived names"
31
+ worktree_path=""
32
+ compose_project="$(compose_project_for_task "$task_id")"
33
+ projects_json="[]"
34
+ else
35
+ worktree_path=$(jq -r '.[0].worktree_path // ""' <<<"$task_json")
36
+ compose_project=$(jq -r '.[0].compose_project // ""' <<<"$task_json")
37
+ projects_json=$(db_get_projects_for_task "$task_id" 2>/dev/null) \
38
+ || projects_json="[]"
28
39
  fi
29
-
30
- worktree_path=$(jq -r '.[0].worktree_path // ""' <<<"$task_json")
31
- compose_project=$(jq -r '.[0].compose_project // ""' <<<"$task_json")
32
-
33
- projects_json=$(db_get_projects_for_task "$task_id")
34
40
  projects_root="$UAI_WORKSPACE_ROOT/projects"
35
41
 
36
42
  # task_dir falls back to the derived path when the row's worktree_path is
@@ -198,7 +198,15 @@ while IFS= read -r project_obj; do
198
198
  [ -n "$project_default" ] || project_default="main"
199
199
 
200
200
  mkdir -p "$(dirname "$worktree_target")"
201
- if git -C "$mirror_dir" rev-parse --verify --quiet \
201
+ if [ -e "$worktree_target/.git" ]; then
202
+ # Resume (ADR-028): the task worktree survived the stop — reuse it as-is
203
+ # (a linked worktree's .git is a FILE, hence -e). Re-running `git
204
+ # worktree add` would refuse ("already exists" / branch exists) and
205
+ # errored EVERY resume of a worktree-preserving stop within seconds
206
+ # (found live 2026-07-21). The task branch and any uncommitted work are
207
+ # exactly what the user expects back.
208
+ log "worktree for $project_slug already present — resuming with it"
209
+ elif git -C "$mirror_dir" rev-parse --verify --quiet \
202
210
  "refs/remotes/origin/$project_default" >/dev/null 2>&1; then
203
211
  # Normal case: branch the task worktree off origin/<defaultBranch>.
204
212
  step "WORKTREE_FAILED" "git worktree add ($project_id)"
@@ -537,7 +545,21 @@ if [ -f "$uai_identity_key" ]; then
537
545
  >/dev/null 2>&1 || true
538
546
  fi
539
547
 
540
- docker exec "$app_container" /usr/local/bin/uai-init >/dev/null
548
+ # uai-init (workspace deps + code-server) is re-runnable, and its death must
549
+ # not error a task whose container is already up (graceful degradation): a
550
+ # SIGKILLed exec session (observed live 2026-07-21, exit 137 with no timeout
551
+ # anywhere on this path) used to fail the whole task over a step anyone can
552
+ # re-run. Retry once, then come up DEGRADED with a warning the host surfaces
553
+ # as a system note in the task feed.
554
+ init_warning=""
555
+ if ! docker exec "$app_container" /usr/local/bin/uai-init >/dev/null; then
556
+ log "warning: uai-init failed — retrying once"
557
+ sleep 2
558
+ if ! docker exec "$app_container" /usr/local/bin/uai-init >/dev/null; then
559
+ init_warning="Workspace init (deps + editor) failed twice — the task is up, but the editor and installed dependencies may be missing. Retry from a task terminal with: /usr/local/bin/uai-init"
560
+ log "warning: $init_warning"
561
+ fi
562
+ fi
541
563
 
542
564
  # -----------------------------------------------------------------------------
543
565
  # 7. Discover code-server + preview ports, mark running, unlock.
@@ -580,7 +602,9 @@ emit_ok "$(jq -nc \
580
602
  --arg cp "$compose_project" \
581
603
  --arg wp "$task_dir" \
582
604
  --arg csport "${code_server_port:-}" \
605
+ --arg iw "${init_warning}" \
583
606
  --argjson previewPorts "$preview_ports_runtime_json" \
584
607
  '{composeProject:$cp,worktreePath:$wp}
585
608
  + (if $csport == "" then {} else {codeServerPort:($csport|tonumber)} end)
609
+ + (if $iw == "" then {} else {initWarning:$iw} end)
586
610
  + {previewPorts:$previewPorts}')"
package/src/index.ts CHANGED
@@ -123,6 +123,14 @@ export const hostCommands: HostCommands = {
123
123
  if (result.ok) {
124
124
  recordTaskUpResult(input.task.id, result.value);
125
125
  recordHostEvent(input.task.id, "task.started");
126
+ // Degraded start (uai-init failed twice): the task is up, but say so
127
+ // in the feed — a silent half-start reads as a broken product.
128
+ if (result.value.initWarning) {
129
+ getOrchestrator().emitSystemNote(
130
+ input.task.id,
131
+ result.value.initWarning,
132
+ );
133
+ }
126
134
  // GitHub auth for the container is best-effort (ADR-027) and runs in the
127
135
  // background — it must never block or fail task-up. Awaiting it here would
128
136
  // couple the command result to a network token-exchange: a slow/hung
package/src/protocol.ts CHANGED
@@ -93,6 +93,8 @@ export interface TaskUpResult {
93
93
  worktreePath: string;
94
94
  codeServerPort?: number;
95
95
  previewPorts?: Array<{ name: string; hostPort: number }>;
96
+ /** Degraded start: uai-init failed twice; the task is up without deps/editor. */
97
+ initWarning?: string;
96
98
  }
97
99
 
98
100
  export interface TaskDownResult {