@runuai/host 0.9.143 → 0.9.145

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.
@@ -55,6 +55,7 @@ import type {
55
55
  // Sourced from that picker; UPDATE WHEN MODELS CHANGE. Order = display order
56
56
  // in the cloud picker. Legacy models remain reachable via config.toml.
57
57
  const CODEX_MODELS = [
58
+ "gpt-6-astra",
58
59
  "gpt-5.6-sol",
59
60
  "gpt-5.6-terra",
60
61
  "gpt-5.6-luna",
@@ -137,6 +137,7 @@ import type {
137
137
  HostEvent,
138
138
  } from "../src/protocol";
139
139
  import {
140
+ type DeliveryOrigin,
140
141
  type TaskStartStage,
141
142
  HOST_ACCOUNT_SCOPE,
142
143
  MAX_AGENT_ID_CHARS,
@@ -2333,6 +2334,9 @@ export class Orchestrator {
2333
2334
  taskId: string,
2334
2335
  agentId: string,
2335
2336
  text: string,
2337
+ /** ADR-141 rung 1: only a human turn is shadow-routed. An older cloud
2338
+ * sends nothing, which reads as "not human" and keeps the pass off. */
2339
+ origin?: DeliveryOrigin,
2336
2340
  ): Promise<{ ok: true } | { ok: false; error: string }> {
2337
2341
  // Slash command: re-run GitHub auth setup for this task (ADR-027). Handled
2338
2342
  // before sessions so it works even when an agent isn't ready.
@@ -2372,8 +2376,11 @@ export class Orchestrator {
2372
2376
  }
2373
2377
  // ADR-141 rung 1: after the turn is on its way, ask what Auto would have
2374
2378
  // done and report it. Fire-and-forget by design — it must never delay or
2375
- // fail a delivery, so every error ends here.
2376
- if (channel.routingShadow) {
2379
+ // fail a delivery, so every error ends here. HUMAN turns only: idle
2380
+ // nudges, secretary dispatches to crew and wake redeliveries all arrive
2381
+ // through this same command, and classifying them would poison the very
2382
+ // data the shadow pass exists to collect.
2383
+ if (channel.routingShadow && origin === "human") {
2377
2384
  void this.shadowRouteDelivery(channel, agentId, text).catch((err) => {
2378
2385
  console.warn(
2379
2386
  `[routing] shadow pass failed for ${channel.taskId}/${agentId}: ${
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runuai/host",
3
- "version": "0.9.143",
3
+ "version": "0.9.145",
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>",
@@ -91,11 +91,9 @@ project_env_json=$(printf '%s' "$project_env_json" | jq -c '.')
91
91
 
92
92
  task_dir="$UAI_WORKSPACE_ROOT/tasks/$task_id"
93
93
  task_workspace="$task_dir/workspace"
94
+ # The HOST control tree. Deliberately not bind-mounted: the container sees only
95
+ # $task_workspace. Anything an agent must reach belongs under $task_workspace/.uai.
94
96
  task_uai_dir="$task_dir/.uai"
95
- # Agents are told to save browser screenshots and other artifacts under
96
- # /workspace/.uai/attachments/, but nothing created the directory — every
97
- # first browser_take_screenshot hit ENOENT (found by an agent in the first
98
- # apple-container task, 2026-08-24). Created with the .uai dir below.
99
97
  projects_root="$UAI_WORKSPACE_ROOT/projects"
100
98
  compose_project=$(compose_project_for_task "$task_id")
101
99
  app_container=$(app_container_for_task "$task_id")
@@ -880,14 +878,19 @@ shared_owner_user=$(jq -r '.[0].owner_user_id // ""' <<<"$task_json")
880
878
  shared_owner_org=$(printf '%s' "$shared_owner_org" | tr ':' '_')
881
879
  shared_owner_user=$(printf '%s' "$shared_owner_user" | tr ':' '_')
882
880
  shared_root="$UAI_WORKSPACE_ROOT/shared"
881
+ # The workspace's own .uai is the only one the container can read (/workspace/.uai):
882
+ # the CLI, the chat log and the attachments agents are told to write screenshots
883
+ # into all live here. Create it unconditionally — it used to be made only when
884
+ # shared files were on, and `attachments` was made in the unmounted control tree,
885
+ # so every first browser_take_screenshot hit ENOENT (first seen 2026-08-24).
886
+ reject_unsafe_host_directory "$task_workspace/.uai" \
887
+ "the task workspace marker directory"
888
+ mkdir -p "$task_workspace/.uai/attachments"
883
889
  if [ "$shared_files_mode" != "off" ]; then
884
890
  [ -n "$shared_owner_org" ] && mkdir -p "$shared_root/orgs/$shared_owner_org"
885
891
  [ -n "$shared_owner_user" ] && mkdir -p "$shared_root/users/$shared_owner_user"
886
892
  # Marker for the preamble: only containers that actually carry the mounts
887
893
  # get the "## Shared files" briefing (pre-feature containers don't).
888
- reject_unsafe_host_directory "$task_workspace/.uai" \
889
- "the task workspace marker directory"
890
- mkdir -p "$task_workspace/.uai"
891
894
  : > "$task_workspace/.uai/files-mounted"
892
895
  fi
893
896
 
@@ -1328,7 +1331,7 @@ done < <(jq -c '.[]' <<<"$projects_json")
1328
1331
  # uid-501 macOS host. The sanitizer above repairs its rewritten control files
1329
1332
  # on every later resume.
1330
1333
  step "WORKTREE_FAILED" "share task files with container"
1331
- mkdir -p "$task_uai_dir" "$task_uai_dir/attachments"
1334
+ mkdir -p "$task_uai_dir"
1332
1335
  # The shared group may be a broad host group (for example macOS `staff`). Keep
1333
1336
  # its writable leaves behind host-only parents; Docker bind-mounts the exact
1334
1337
  # workspace/repository roots, so node does not need host-path traversal here.
@@ -1376,7 +1379,7 @@ fi
1376
1379
 
1377
1380
  step "RENDER_FAILED" "generate compose"
1378
1381
 
1379
- mkdir -p "$task_uai_dir" "$task_uai_dir/attachments"
1382
+ mkdir -p "$task_uai_dir"
1380
1383
 
1381
1384
  STANDARD_CONTAINER_DIR="${UAI_STANDARD_CONTAINER_ASSETS_DIR:-$SCRIPT_DIR/../../images/standard/container}"
1382
1385
  if ! STANDARD_CONTAINER_DIR=$(cd "$STANDARD_CONTAINER_DIR" && pwd -P); then
package/src/index.ts CHANGED
@@ -1198,7 +1198,7 @@ export const hostCommands: HostCommands = {
1198
1198
  });
1199
1199
  },
1200
1200
 
1201
- async channelDeliver(ctx, taskId, agentId, text) {
1201
+ async channelDeliver(ctx, taskId, agentId, text, origin) {
1202
1202
  logCommand(ctx, "channelDeliver", taskId, agentId);
1203
1203
  const runtimeFailure = runtimeUnavailable();
1204
1204
  if (runtimeFailure) return runtimeFailure;
@@ -1206,7 +1206,7 @@ export const hostCommands: HostCommands = {
1206
1206
  // An inbound turn is activity: the idle veto must cover the gap between
1207
1207
  // this delivery landing and the agent's first output event.
1208
1208
  getOrchestrator().markChannelActivity(taskId);
1209
- const result = await getOrchestrator().deliver(taskId, agentId, text);
1209
+ const result = await getOrchestrator().deliver(taskId, agentId, text, origin);
1210
1210
  if (!result.ok) {
1211
1211
  return {
1212
1212
  ok: false,
package/src/protocol.ts CHANGED
@@ -1467,6 +1467,11 @@ export interface HostCommands {
1467
1467
  taskId: string,
1468
1468
  agentId: string,
1469
1469
  text: string,
1470
+ /** ADR-141 rung 1: who authored this delivery. Only "human" turns are
1471
+ * shadow-routed — nudges, secretary dispatches to crew and wake
1472
+ * redeliveries are not messages a router would ever classify. Absent
1473
+ * from an older cloud, which the host reads as "not a human turn". */
1474
+ origin?: DeliveryOrigin,
1470
1475
  ): Promise<HostCommandResult<void>>;
1471
1476
  channelResolvePermission(
1472
1477
  ctx: CommandContext,
@@ -1717,6 +1722,9 @@ export type CloudToHost =
1717
1722
  | { kind: "lease.grant"; opId: string; lease: TaskLease }
1718
1723
  | { kind: "lease.deny"; opId: string; error: string };
1719
1724
 
1725
+ /** ADR-141: provenance of a channelDeliver. */
1726
+ export type DeliveryOrigin = "human" | "agent" | "system";
1727
+
1720
1728
  /** One MCP-connection operation (ADR-057), executed by the host. */
1721
1729
  /**
1722
1730
  * ADR-133: the short-lived AWS credentials a task machine runs on — an STS