@runuai/host 0.9.101 → 0.9.102

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runuai/host",
3
- "version": "0.9.101",
3
+ "version": "0.9.102",
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;