eve-hub 0.10.2 → 0.12.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # eve-hub
2
2
 
3
+ ## 0.12.0
4
+
5
+ ### Minor Changes
6
+
7
+ - A deployed bot's calls to the hub carry its project's OIDC token in `x-vercel-trusted-oidc-idp-token` as well as `Authorization`. The hub project's Deployment Protection takes it as a Trusted Source, so a bots project no longer needs `HUB_PROTECTION_BYPASS`; a deployed bot never sends `HUB_PROTECTION_BYPASS`, even while its deployment still holds the env, so deleting the env needs no redeploy. A bot that runs on a Mac still sends the secret it is given.
8
+
9
+ ## 0.11.0
10
+
11
+ ### Minor Changes
12
+
13
+ - A bot's roster entry can name whose credential its model calls carry: `"modelAuth": "project" | "user" | "codex"` (default `project`, the workspace's bots project). `hub.workspace.json` rejects any other value, and `user` or `codex` on a bot that does not run on the Mac. Nothing acts on `user` or `codex` yet; this release only lets a workspace carry the key before the hub starts writing it.
14
+
3
15
  ## 0.10.2
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -61,7 +61,7 @@ A deployed workspace (lib/workspace-template.mjs, staged by hosted-hub/scripts/s
61
61
 
62
62
  Releases go through Changesets, as in eve. A PR that changes the kit adds a changeset (`pnpm changeset`) and applies it (`pnpm changeset version`); when it merges, `.github/workflows/release.yml` runs `changeset publish` (no bot commits to main: its ruleset allows only PR merges): the kit's `prepack` stages the package into `dist/npm` (its `publishConfig.directory`), npm gets it, and the `eve-hub@<version>` GitHub release is created. npm auth is the repo's `NPM_TOKEN` secret (an npm automation token). The template pins the version this checkout carries.
63
63
 
64
- The template's main gets a Vercel Sandbox and only the kit stubs that run without the hub's machine (lib/workspace-template.mjs `DEPLOYED_KIT_FILES`; eve's own bash replaces the kit's). The template's builder also mounts eve-code (`eve/extensions/code`: `apply_patch`, `grep`, the review skills, a read-only worker) and `eve/extensions/git` (`git({})`), both with no `github` option. The bot uses `gh` and `git` from bash, whose GitHub token is the sandbox firewall's (the Vercel sandbox `create_agent` writes, lib/agent-template.mjs `sandboxFile`), and no `gh` tool is contributed. A deployed workspace's eve is the one pin in `hosted-hub/bots/package.json`, which staging copies over the template's (an eve snapshot with both `eve/world-hub` and `eve/extensions/git`; `hosted-hub/test/stage-workspace.test.mjs` checks the staged imports resolve against it). The hub and local bots stay on the hub's eve. The published tarball (`scripts/pack.mjs`) leaves out the kit__worker extension, which mounts only on the hub's machine. The hub reaches each bot's eve channel with `Authorization: Bearer $BOT_TOKEN` (`bot-token.mjs`); the kit's calls to the hub (`registry-fetch.mjs`, at `ROSTER_REGISTRY_URL`) carry `Authorization: Bearer $HUB_TOKEN` and, since the hosted hub keeps Vercel Authentication on, `x-vercel-protection-bypass: $HUB_PROTECTION_BYPASS` (the hub project's automation bypass secret), each when set.
64
+ The template's main gets a Vercel Sandbox and only the kit stubs that run without the hub's machine (lib/workspace-template.mjs `DEPLOYED_KIT_FILES`; eve's own bash replaces the kit's). The template's builder also mounts eve-code (`eve/extensions/code`: `apply_patch`, `grep`, the review skills, a read-only worker) and `eve/extensions/git` (`git({})`), both with no `github` option. The bot uses `gh` and `git` from bash, whose GitHub token is the sandbox firewall's (the Vercel sandbox `create_agent` writes, lib/agent-template.mjs `sandboxFile`), and no `gh` tool is contributed. A deployed workspace's eve is the one pin in `hosted-hub/bots/package.json`, which staging copies over the template's (an eve snapshot with both `eve/world-hub` and `eve/extensions/git`; `hosted-hub/test/stage-workspace.test.mjs` checks the staged imports resolve against it). The hub and local bots stay on the hub's eve. The published tarball (`scripts/pack.mjs`) leaves out the kit__worker extension, which mounts only on the hub's machine. The hub reaches each bot's eve channel with `Authorization: Bearer $BOT_TOKEN` (`bot-token.mjs`); the kit's calls to the hub (`registry-fetch.mjs`, at `ROSTER_REGISTRY_URL`) carry `Authorization: Bearer $HUB_TOKEN` when set. The hosted hub keeps Vercel Authentication on: a deployed bot sends its project's OIDC token in `x-vercel-trusted-oidc-idp-token` too (the hub project lists each bots project in its Trusted Sources, `hosted-hub/src/onboard/trust.ts` `trustBots`, and never the bypass secret, even while its deployment still holds the env), and a bot on a Mac sends `x-vercel-protection-bypass: $HUB_PROTECTION_BYPASS` (the hub project's automation bypass secret) when it has one.
65
65
 
66
66
  ## Vercel Kit connection
67
67
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eve-hub",
3
- "version": "0.10.2",
3
+ "version": "0.12.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Bot-side code shared by every hub agent: registry tools, the roster hook, the eve channel, the host sandbox and browser, and model helpers.",
6
6
  "type": "module",
@@ -1,8 +1,10 @@
1
1
  // Registry-relative fetch for the kit, using ROSTER_REGISTRY_URL.
2
2
  // HUB_TOKEN is this bot's credential for a hosted hub; unset locally.
3
3
  // HUB_PROTECTION_BYPASS is the hosted hub project's Protection Bypass for
4
- // Automation secret: the hub keeps Vercel Authentication on, and the bots (on
5
- // the same team) pass it with x-vercel-protection-bypass. Unset locally.
4
+ // Automation secret, sent with x-vercel-protection-bypass by a bot that runs on
5
+ // a Mac (a deployed bot's project token passes the hub's Deployment Protection
6
+ // instead: hubHeaders, which a deployed bot sends whether or not the project
7
+ // still holds the secret). Unset locally.
6
8
  // `hub restart` keeps the bots running while the registry is replaced, so a
7
9
  // turn can call a tool during the few seconds no registry is listening.
8
10
  // Retried, with backoff (250 ms doubling to 2 s) for up to 30 s:
@@ -34,7 +36,7 @@ export const sandboxRegistry = () => (process.env.HUB_SANDBOX_SERVICES === "none
34
36
  export function registryHeaders(init, env = process.env) {
35
37
  const headers = new Headers(init);
36
38
  if (env.HUB_TOKEN && !headers.has("authorization")) headers.set("authorization", `Bearer ${env.HUB_TOKEN}`);
37
- if (env.HUB_PROTECTION_BYPASS) headers.set("x-vercel-protection-bypass", env.HUB_PROTECTION_BYPASS);
39
+ if (env.HUB_PROTECTION_BYPASS && !env.VERCEL) headers.set("x-vercel-protection-bypass", env.HUB_PROTECTION_BYPASS);
38
40
  return Object.fromEntries(headers);
39
41
  }
40
42
 
@@ -59,11 +61,15 @@ const DROPPED = new Set(["ECONNRESET", "EPIPE", "UND_ERR_SOCKET"]);
59
61
 
60
62
  // Every call a bot makes to the hub: a deployed bot is the caller its own project's token says (the hub checks it: api/auth.ts);
61
63
  // elsewhere HUB_TOKEN is the credential, if any. The one place a call's headers are made.
64
+ // The same token also passes the hub's Deployment Protection, which trusts the bots projects (a Trusted Source) and takes it in
65
+ // its own header: no bypass secret.
62
66
  export async function hubHeaders(init, env = process.env) {
63
67
  const merged = new Headers(init);
64
- if (env.VERCEL && !merged.has("authorization")) {
68
+ if (env.VERCEL) {
65
69
  const { getVercelOidcToken } = await import("@vercel/oidc");
66
- merged.set("authorization", `Bearer ${await getVercelOidcToken()}`);
70
+ const token = await getVercelOidcToken();
71
+ if (!merged.has("authorization")) merged.set("authorization", `Bearer ${token}`);
72
+ merged.set("x-vercel-trusted-oidc-idp-token", token);
67
73
  }
68
74
  return registryHeaders(merged, env);
69
75
  }
package/workspace.mjs CHANGED
@@ -47,9 +47,12 @@ export const DOMAIN = /^[a-z][a-z0-9-]*(\/[a-z][a-z0-9-]*)*$/;
47
47
  export const NAME = /^[a-z][a-z0-9-]{1,30}$/;
48
48
  export const HUMAN_NAME = /^[a-z][a-z0-9-]{0,30}$/;
49
49
  const COLOR = /^#[0-9a-f]{3}([0-9a-f]{3})?$/i;
50
- const AGENT_KEYS = new Set(["name", "model", "role", "color", "subagents", "subagentCap", "limits", "port", "runsOn", "domains", "kind"]);
50
+ const AGENT_KEYS = new Set(["name", "model", "role", "color", "subagents", "subagentCap", "limits", "port", "runsOn", "modelAuth", "domains", "kind"]);
51
51
 
52
52
  export const RUNS_ON = ["hosted", "local"];
53
+ // Whose credential a bot's model calls carry: the workspace's bots project (default), the human's own
54
+ // Vercel sign-in, or the human's ChatGPT plan. Fixed at definition, like `runsOn`.
55
+ const MODEL_AUTH = ["project", "user", "codex"];
53
56
  // The bot's place in the team (team.mjs draws the handoffs between kinds).
54
57
  export const KINDS = ["coordinator", "planner", "builder", "reviewer", "git-ops", "demo"];
55
58
 
@@ -104,6 +107,7 @@ export function parseWorkspace(data, file = WORKSPACE_FILE) {
104
107
  // Where the bot runs: on the hosted bots project (default), or on the human's Mac, started by
105
108
  // the app's local runner. Fixed at definition.
106
109
  if (agent.runsOn !== undefined && !RUNS_ON.includes(agent.runsOn)) fail(file, `${label}.runsOn must be "hosted" or "local"`);
110
+ if (agent.modelAuth !== undefined && !MODEL_AUTH.includes(agent.modelAuth)) fail(file, `${label}.modelAuth must be one of ${MODEL_AUTH.join(", ")}`);
107
111
  if (agent.kind !== undefined && !KINDS.includes(agent.kind)) fail(file, `${label}.kind must be one of ${KINDS.join(", ")}`);
108
112
  if (agent.domains !== undefined && (!Array.isArray(agent.domains) || agent.domains.some((d) => typeof d !== "string" || !DOMAIN.test(d)))) fail(file, `${label}.domains must be an array of memory folders like "ui" or "ui/motion"`);
109
113
  const unknown = Object.keys(agent).filter((k) => !AGENT_KEYS.has(k));
@@ -115,6 +119,10 @@ export function parseWorkspace(data, file = WORKSPACE_FILE) {
115
119
  if (!RUNS_ON.includes(data.runsOn)) fail(file, '"runsOn" must be "hosted" or "local"');
116
120
  out.runsOn = data.runsOn;
117
121
  }
122
+ // `user` and `codex` are credentials only the human's Mac holds; a hosted bot has the bots project's.
123
+ for (const a of agents) {
124
+ if (a.modelAuth !== undefined && a.modelAuth !== "project" && agentRunsOn(out, a.name) !== "local") fail(file, `${a.name}: modelAuth \"${a.modelAuth}\" needs runsOn \"local\"`);
125
+ }
118
126
  if (data.humans !== undefined) {
119
127
  if (!Array.isArray(data.humans) || !data.humans.length) fail(file, "\"humans\" must be a non-empty array");
120
128
  out.humans = data.humans.map((h, i) => {
package/world.mjs CHANGED
@@ -10660,14 +10660,16 @@ import { createTursoWorld } from "./vendor/world-turso.mjs";
10660
10660
  function registryHeaders(init, env = process.env) {
10661
10661
  const headers = new Headers(init);
10662
10662
  if (env.HUB_TOKEN && !headers.has("authorization")) headers.set("authorization", `Bearer ${env.HUB_TOKEN}`);
10663
- if (env.HUB_PROTECTION_BYPASS) headers.set("x-vercel-protection-bypass", env.HUB_PROTECTION_BYPASS);
10663
+ if (env.HUB_PROTECTION_BYPASS && !env.VERCEL) headers.set("x-vercel-protection-bypass", env.HUB_PROTECTION_BYPASS);
10664
10664
  return Object.fromEntries(headers);
10665
10665
  }
10666
10666
  async function hubHeaders(init, env = process.env) {
10667
10667
  const merged = new Headers(init);
10668
- if (env.VERCEL && !merged.has("authorization")) {
10668
+ if (env.VERCEL) {
10669
10669
  const { getVercelOidcToken: getVercelOidcToken2 } = await Promise.resolve().then(() => __toESM(require_dist3(), 1));
10670
- merged.set("authorization", `Bearer ${await getVercelOidcToken2()}`);
10670
+ const token = await getVercelOidcToken2();
10671
+ if (!merged.has("authorization")) merged.set("authorization", `Bearer ${token}`);
10672
+ merged.set("x-vercel-trusted-oidc-idp-token", token);
10671
10673
  }
10672
10674
  return registryHeaders(merged, env);
10673
10675
  }