@runuai/host 0.9.101 → 0.9.103

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.
@@ -537,6 +537,16 @@ async function copyDirInto(
537
537
  }
538
538
  };
539
539
  await run(["/bin/mkdir", "-p", containerDir], `mkdir ${containerDir}`);
540
+ // The mkdir above runs as root, but a MACHINE copy extracts as the ssh
541
+ // user (node) — into a root-owned directory it dies with "Cannot open:
542
+ // Permission denied" and the account never authenticates (live
543
+ // 2026-09-07, first scoped Codex account on the shared host). Hand the
544
+ // directory to node BEFORE the copy; this also repairs a directory an
545
+ // earlier failed attempt left root-owned on a still-running machine.
546
+ await run(
547
+ ["/bin/chown", "-R", "node:node", containerDir],
548
+ `chown -R node:node ${containerDir}`,
549
+ );
540
550
  await environment.copy({
541
551
  direction: "into",
542
552
  source: hostDir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runuai/host",
3
- "version": "0.9.101",
3
+ "version": "0.9.103",
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>",
package/src/main.ts CHANGED
@@ -229,6 +229,7 @@ import {
229
229
  type TaskDownInput,
230
230
  type TaskLaunchInput,
231
231
  type FilesOpInput,
232
+ engineAccountScopeFromWire,
232
233
  } from "./protocol";
233
234
 
234
235
  setContainerRuntimeOperationalCheck(() => containerRuntimeProblem() === null);
@@ -3205,6 +3206,9 @@ function expectChannelEnsureInput(
3205
3206
  if (typeof input.globalContext === "string") {
3206
3207
  out.globalContext = input.globalContext;
3207
3208
  }
3209
+ // ADR-131 D3: same scope the task launched with (refreshChannel adopts it).
3210
+ const channelScope = engineAccountScopeFromWire(input.engineAccountScope);
3211
+ if (channelScope !== undefined) out.engineAccountScope = channelScope;
3208
3212
  // ADR-083: secretary identity drives the role-specific transcript preamble.
3209
3213
  // Both are optional for wire compatibility with older clouds.
3210
3214
  const mode = parseChannelMode(input.mode);
@@ -3307,6 +3311,14 @@ function expectTaskCommandTask(value: unknown): TaskCommandTask {
3307
3311
  ) {
3308
3312
  out.sharedFiles = row.sharedFiles;
3309
3313
  }
3314
+ // ADR-131 D3: the workspace whose engine accounts this task resolves.
3315
+ // Dropping it here silently re-scoped every shared-host task to the host's
3316
+ // own credentials (live 2026-09-07) — see engineAccountScopeFromWire.
3317
+ const taskScope = engineAccountScopeFromWire(
3318
+ row.engineAccountScope,
3319
+ "task.engineAccountScope",
3320
+ );
3321
+ if (taskScope !== undefined) out.engineAccountScope = taskScope;
3310
3322
  return out;
3311
3323
  }
3312
3324
 
package/src/protocol.ts CHANGED
@@ -121,6 +121,26 @@ export function isEngineAccountScope(value: unknown): value is string {
121
121
  (typeof value === "string" && ORG_ACCOUNT_SCOPE_RE.test(value))
122
122
  );
123
123
  }
124
+
125
+ /**
126
+ * ADR-131 D3: read an optional `engineAccountScope` off a wire object.
127
+ * Returns the scope when present and well-formed, undefined when absent, and
128
+ * THROWS on a malformed value — a tenant scope must never degrade silently
129
+ * to the host's credentials. LIVE 2026-09-07: the host's task-up and
130
+ * channel-ensure validators rebuilt their inputs field by field and never
131
+ * copied this key, so every shared-host task ran in the HOST scope — Codex
132
+ * had no account there and looped "Reconnecting" while the cloud had sent
133
+ * `org:<id>` all along. Every `expect*Input` that carries a scope must call
134
+ * this; `host-agent/src/protocol.test.ts` pins the shape.
135
+ */
136
+ export function engineAccountScopeFromWire(
137
+ value: unknown,
138
+ field = "engineAccountScope",
139
+ ): string | undefined {
140
+ if (value === undefined || value === null) return undefined;
141
+ if (isEngineAccountScope(value)) return value;
142
+ throw new Error(`invalid ${field}`);
143
+ }
124
144
  export const MAX_ENGINE_CONNECT_SECRET_CHARS = 8_192;
125
145
  export const MAX_ENGINE_CONNECT_LINE_CHARS = 512;
126
146
  export const MAX_ENGINE_CATALOG_ENTRIES = 8;