@runuai/host 0.8.20 → 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.20",
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
@@ -545,7 +545,21 @@ if [ -f "$uai_identity_key" ]; then
545
545
  >/dev/null 2>&1 || true
546
546
  fi
547
547
 
548
- 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
549
563
 
550
564
  # -----------------------------------------------------------------------------
551
565
  # 7. Discover code-server + preview ports, mark running, unlock.
@@ -588,7 +602,9 @@ emit_ok "$(jq -nc \
588
602
  --arg cp "$compose_project" \
589
603
  --arg wp "$task_dir" \
590
604
  --arg csport "${code_server_port:-}" \
605
+ --arg iw "${init_warning}" \
591
606
  --argjson previewPorts "$preview_ports_runtime_json" \
592
607
  '{composeProject:$cp,worktreePath:$wp}
593
608
  + (if $csport == "" then {} else {codeServerPort:($csport|tonumber)} end)
609
+ + (if $iw == "" then {} else {initWarning:$iw} end)
594
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 {