eve-hub 0.4.0 → 0.4.2
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/README.md +13 -18
- package/hub-path.mjs +2 -2
- package/main-instructions.mjs +2 -2
- package/model-credential.mjs +44 -0
- package/package.json +4 -2
- package/roster-model.mjs +8 -0
- package/workspace-roster.mjs +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# eve-hub
|
|
2
2
|
|
|
3
3
|
The bot-side code every hub agent shares (milestone M3 in [`docs/architecture`](../../docs/architecture/README.md#milestones); D0, D12). Bots live in a workspace (their own repo, [`docs/workspace.md`](../../docs/workspace.md)); each keeps a one-line stub per file and the code lives here once.
|
|
4
4
|
|
|
@@ -9,14 +9,14 @@ The bot-side code every hub agent shares (milestone M3 in [`docs/architecture`](
|
|
|
9
9
|
| `hooks/sandbox.ts` | Migrates legacy Docker sessions to the host sandbox (`defineHook`) | `agent/hooks/sandbox.ts` |
|
|
10
10
|
| `instructions/main.ts`, `main-instructions.mjs` | main's instructions (`defineInstructions`), rendered at build time from the kit's `templates/main-instructions.md` + `workspace-protocol.md`, so no workspace carries a copy | `main`'s `agent/instructions.ts`; workspace-only additions in `agent/instructions/*.md` |
|
|
11
11
|
| `instructions/protocol.ts` | The shared protocol alone, rendered from `templates/workspace-protocol.md` | every other bot's `agent/instructions/protocol.ts`, appended after its own `instructions.md` (who the bot is) |
|
|
12
|
-
| `memory/workspace.ts`, `memory.mjs` | an eve memory slot (default export, constant `workspace` scope) whose provider recalls the workspace's `memory/<id>.md` entries, one message each, at every turn start and after compaction, only the ones whose text changed since the session last saw them, as new messages after the history; `validateMemory(root)` for a workspace's tests | every bot's (main too) `agent/memory/workspace.ts`: `export { default } from "
|
|
13
|
-
| `memory/bot.ts` | the same for the bot's own `agents/<bot>/memory/<id>.md` entries; the bot is found from its directory (`findBotDir`), scope is its name | every bot's `agent/memory/bot.ts`: `export { default } from "
|
|
12
|
+
| `memory/workspace.ts`, `memory.mjs` | an eve memory slot (default export, constant `workspace` scope) whose provider recalls the workspace's `memory/<id>.md` entries, one message each, at every turn start and after compaction, only the ones whose text changed since the session last saw them, as new messages after the history; `validateMemory(root)` for a workspace's tests | every bot's (main too) `agent/memory/workspace.ts`: `export { default } from "eve-hub/memory/workspace"` |
|
|
13
|
+
| `memory/bot.ts` | the same for the bot's own `agents/<bot>/memory/<id>.md` entries; the bot is found from its directory (`findBotDir`), scope is its name | every bot's `agent/memory/bot.ts`: `export { default } from "eve-hub/memory/bot"` |
|
|
14
14
|
| `channels/eve-roster-user.ts` | `eveChannel({ auth: [rosterUser, localDev()], turnPolicy: "queue" })`: queued mentions, and the registry's `x-roster-user` human as a user principal | every bot's `agent/channels/eve.ts` |
|
|
15
15
|
| `channels/eve.ts` | Re-exports `eve-roster-user.ts`, for stubs written before every bot shared one channel | older `agent/channels/eve.ts` stubs |
|
|
16
16
|
| `tools/disabled.ts` | `disableTool()`: removes an eve default tool | the read-only preset's `agent/tools/write_file.ts` |
|
|
17
17
|
| `host-sandbox.ts`, `host-browser.ts` | `HostSandbox` (the host machine is the sandbox) and each session's headless Chromium | `agent/sandbox.ts` imports `HostSandbox` |
|
|
18
18
|
| `roster-model.mjs` | `bindRoster(workspace)`: `rosterModel(name, …)` (the dynamic `model` for agent.ts), `rosterLimits`, `agents` and `subagentCap`, for the `hub.workspace.json` the bot imports; `rosterModel` / `rosterLimits` read from disk for bots scaffolded before it; `createRosterModel({ agents, subagentCap })`: the same for a given roster | `bindRoster(workspace)` in agent.ts |
|
|
19
|
-
| `session-limits.mjs` | `sessionLimits(roster, name)`: a bot's eve `limits` (no session timeout, per-session usage limits from `hub.workspace.json` over the default backstop) | `limits: rosterLimits("<name>")` from
|
|
19
|
+
| `session-limits.mjs` | `sessionLimits(roster, name)`: a bot's eve `limits` (no session timeout, per-session usage limits from `hub.workspace.json` over the default backstop) | `limits: rosterLimits("<name>")` from `eve-hub/roster-model` |
|
|
20
20
|
| `workspace.mjs` | Reads and validates `hub.workspace.json`, assigns ports, finds a bot's workspace (`HUB_WORKSPACE`, else up from cwd) | the daemon (`lib/workspace-root.mjs`) and `roster-model.mjs` |
|
|
21
21
|
| `workspace-roster.mjs` | `agents`, `humans`, `SUBAGENT_CAP` of the bot's workspace, read from disk | older subagents that resolve their capped model |
|
|
22
22
|
| `bot-model.mjs`, `subagent-cap.mjs` | Model parsing/selection and the subagent cap | via `roster-model.mjs`; subagents import `subagent-cap.mjs` |
|
|
@@ -29,14 +29,14 @@ eve discovers tools, hooks, and channels by file under `agent/tools`, `agent/hoo
|
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
31
|
// agents/<bot>/agent/tools/rename_thread.ts
|
|
32
|
-
export { default } from "
|
|
32
|
+
export { default } from "eve-hub/tools/rename_thread";
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
`sandbox.ts` stays a small module of its own, because each agent and subagent declares its own `environment`:
|
|
36
36
|
|
|
37
37
|
```ts
|
|
38
38
|
import { defineSandbox } from "eve/sandbox";
|
|
39
|
-
import { HostSandbox } from "
|
|
39
|
+
import { HostSandbox } from "eve-hub/host-sandbox";
|
|
40
40
|
export const environment = HostSandbox.environment();
|
|
41
41
|
export default defineSandbox(() => environment.open());
|
|
42
42
|
```
|
|
@@ -47,23 +47,18 @@ A bot's role is enforced by its files as well as its prose. `create_agent`'s `pr
|
|
|
47
47
|
|
|
48
48
|
## The package import (D12)
|
|
49
49
|
|
|
50
|
-
1. Before spawning the bots, the daemon links the kit (`lib/workspace-link.mjs`): `<workspace>/.hub/
|
|
51
|
-
2. The workspace's `package.json` depends on `"
|
|
52
|
-
3. The stubs use the specifiers the `exports` map serves (
|
|
50
|
+
1. Before spawning the bots, the daemon links the kit (`lib/workspace-link.mjs`): `<workspace>/.hub/eve-hub` and `<workspace>/node_modules/eve-hub` → `<hub>/packages/agent-kit`. If the workspace has no `eve`/`zod` of its own, they are linked to hub's install too. The daemon only replaces links it created, never a real install.
|
|
51
|
+
2. The workspace's `package.json` depends on `"eve-hub": "link:.hub/eve-hub"` (`workspace init` writes it), so a later `pnpm install` keeps resolving the kit the same way.
|
|
52
|
+
3. The stubs use the specifiers the `exports` map serves (`eve-hub/tools/x`, `eve-hub/host-sandbox`, `eve-hub/roster-model`, …).
|
|
53
53
|
4. `eve` and `zod` are peer dependencies. eve's bundler resolves the kit to its realpath, so the kit's own `eve` imports come from hub's install. When the workspace's eve is linked to hub's, that's one eve (verified with eve 0.69).
|
|
54
54
|
|
|
55
55
|
Bump `version` when a tool's name, schema, or description, the hook's events, or the channel config changes. Hub records each agent's kit version and accepts N and N−1 (D0/D12).
|
|
56
56
|
|
|
57
|
-
## Published
|
|
57
|
+
## Published package (deployed workspaces)
|
|
58
58
|
|
|
59
|
-
A deployed workspace (`node scripts/workspace-template.mjs <dir
|
|
59
|
+
A deployed workspace (`node scripts/workspace-template.mjs <dir>`, lib/workspace-template.mjs) has no hub checkout to link, so it installs the kit from npm, published as [`eve-hub`](https://www.npmjs.com/package/eve-hub): `"eve-hub": "<version>"`. `node packages/agent-kit/scripts/pack.mjs <outDir>` builds the package: every `.ts` module compiled to `.js` + `.d.ts` in place (node won't strip types under `node_modules`), the `exports` map pointed at them, and `templates/` included.
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
T=$(node packages/agent-kit/scripts/pack.mjs /tmp/kit)
|
|
63
|
-
H=$(shasum -a 256 "$T" | cut -c1-12)
|
|
64
|
-
vercel blob put "$T" --access public --rw-token "$BLOB_READ_WRITE_TOKEN" \
|
|
65
|
-
--pathname "agent-kit/$(node -p 'require("./packages/agent-kit/package.json").version')-$H/hub-agent-kit.tgz"
|
|
66
|
-
```
|
|
61
|
+
`.github/workflows/publish-eve-hub.yml` publishes it on every push to main whose kit `version` npm doesn't have yet, authenticated by the repo's `NPM_TOKEN` secret (an npm automation token). Bump `version` to ship a kit change to deployed workspaces; the template pins the version this checkout carries.
|
|
67
62
|
|
|
68
63
|
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 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.
|
|
69
64
|
|
|
@@ -72,7 +67,7 @@ The template's main gets a Vercel Sandbox and only the kit stubs that run withou
|
|
|
72
67
|
An agent opts into the app's Kit connector with `agent/connections/vercel-kit.ts`:
|
|
73
68
|
|
|
74
69
|
```ts
|
|
75
|
-
export { default } from "
|
|
70
|
+
export { default } from "eve-hub/connections/vercel-kit";
|
|
76
71
|
```
|
|
77
72
|
|
|
78
73
|
Set `KIT_CONNECTOR` to the returned Vercel Connect connector UID in Hub's
|
package/hub-path.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// a kit module knows where it is (packages/agent-kit/...). eve bundles
|
|
3
3
|
// authored modules into the workspace's node_modules/.cache, where
|
|
4
4
|
// import.meta.url and import.meta.dirname point into the cache; there it goes
|
|
5
|
-
// through the workspace's link to the kit (node_modules
|
|
5
|
+
// through the workspace's link to the kit (node_modules/eve-hub, which
|
|
6
6
|
// the daemon keeps). Used for hub skills (hub-skill.mjs) and the explainer
|
|
7
7
|
// package that artifact builds shell out to (artifacts/types/change-explainer.mjs).
|
|
8
8
|
import { existsSync, realpathSync } from "node:fs";
|
|
@@ -17,7 +17,7 @@ const hubOf = (kitDir) => join(kitDir, "..", "..");
|
|
|
17
17
|
export function hubPath(rel, { probe = "", env = process.env, cwd = process.cwd() } = {}) {
|
|
18
18
|
const here = join(hubOf(dirname(realpathSync(fileURLToPath(import.meta.url)))), rel);
|
|
19
19
|
if (existsSync(join(here, probe))) return here;
|
|
20
|
-
const link = join(findWorkspaceRoot({ env, cwd }), "node_modules", "
|
|
20
|
+
const link = join(findWorkspaceRoot({ env, cwd }), "node_modules", "eve-hub");
|
|
21
21
|
if (!existsSync(link)) throw new Error(`${join(rel, probe)} isn't in the hub checkout at ${here}, and the workspace has no ${link} to find it through`);
|
|
22
22
|
const linked = join(hubOf(realpathSync(link)), rel);
|
|
23
23
|
if (existsSync(join(linked, probe))) return linked;
|
package/main-instructions.mjs
CHANGED
|
@@ -16,11 +16,11 @@ const templatesOf = (kitDir) => join(kitDir, "templates");
|
|
|
16
16
|
// The templates sit next to the kit in the hub checkout. Run by node, this
|
|
17
17
|
// module knows where it is. eve evaluates instructions from a bundle in the
|
|
18
18
|
// workspace's node_modules/.cache, so there it goes through the workspace's
|
|
19
|
-
// link to the kit (node_modules
|
|
19
|
+
// link to the kit (node_modules/eve-hub, which the daemon keeps).
|
|
20
20
|
export function templatesDir({ env = process.env, cwd = process.cwd() } = {}) {
|
|
21
21
|
const here = templatesOf(dirname(realpathSync(fileURLToPath(import.meta.url))));
|
|
22
22
|
if (existsSync(join(here, HEAD))) return here;
|
|
23
|
-
const link = join(findWorkspaceRoot({ env, cwd }), "node_modules", "
|
|
23
|
+
const link = join(findWorkspaceRoot({ env, cwd }), "node_modules", "eve-hub");
|
|
24
24
|
const linked = templatesOf(realpathSync(link));
|
|
25
25
|
if (existsSync(join(linked, HEAD))) return linked;
|
|
26
26
|
throw new Error(`main's instruction templates aren't at ${here} or ${linked} (through ${link})`);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Hosted bots resolve model ids through the owner's refreshable Vercel sign-in.
|
|
2
|
+
// HUB_TOKEN identifies a hosted bot; the credential endpoint is on its registry.
|
|
3
|
+
// The hub owns expiration and refresh; Gateway rejection invalidates our cache.
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { GatewayAuthenticationError } from "@ai-sdk/gateway";
|
|
6
|
+
import { registryHeaders } from "./registry-fetch.mjs";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The fetch the gateway sends through: the hub's current token and team on
|
|
10
|
+
* each request, and on a 401, one more try with the token the hub hands out
|
|
11
|
+
* after it is told (by sha256) which token Gateway refused.
|
|
12
|
+
*/
|
|
13
|
+
export function hubGatewayFetch(credentialUrl, fetchFn = (...a) => globalThis.fetch(...a), now = Date.now) {
|
|
14
|
+
let current;
|
|
15
|
+
const credential = async (rejected) => {
|
|
16
|
+
if (!rejected && current && now() < current.expiresAt) return current;
|
|
17
|
+
const headers = registryHeaders();
|
|
18
|
+
if (rejected) headers["x-rejected-token-sha256"] = createHash("sha256").update(rejected).digest("hex");
|
|
19
|
+
const response = await fetchFn(credentialUrl, { headers });
|
|
20
|
+
if (response.status === 401) {
|
|
21
|
+
const { error } = await response.json();
|
|
22
|
+
throw new GatewayAuthenticationError({ message: error });
|
|
23
|
+
}
|
|
24
|
+
if (!response.ok) throw new Error(`model credential from the hub: HTTP ${response.status} ${await response.text()}`);
|
|
25
|
+
return (current = await response.json());
|
|
26
|
+
};
|
|
27
|
+
return async (input, init) => {
|
|
28
|
+
const headers = new Headers(init?.headers);
|
|
29
|
+
const send = async (c) => {
|
|
30
|
+
headers.set("authorization", `Bearer ${c.token}`);
|
|
31
|
+
headers.set("x-vercel-ai-gateway-team", c.teamId);
|
|
32
|
+
const response = await fetchFn(input, { ...init, headers });
|
|
33
|
+
if (response.status === 401) current = undefined;
|
|
34
|
+
return response;
|
|
35
|
+
};
|
|
36
|
+
const first = await credential();
|
|
37
|
+
const response = await send(first);
|
|
38
|
+
if (response.status !== 401) return response;
|
|
39
|
+
// A streamed body can't be sent twice, but its rejected token is dropped.
|
|
40
|
+
if (init?.body != null && typeof init.body !== "string") return response;
|
|
41
|
+
await response.body?.cancel();
|
|
42
|
+
return send(await credential(first.token));
|
|
43
|
+
};
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eve-hub",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"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.",
|
|
3
|
+
"version": "0.4.2",
|
|
5
4
|
"license": "Apache-2.0",
|
|
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",
|
|
7
7
|
"exports": {
|
|
8
8
|
"./package.json": "./package.json",
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"eve": "*",
|
|
31
|
+
"ai": "^7.0.105",
|
|
32
|
+
"@ai-sdk/gateway": "^4.0.100",
|
|
31
33
|
"zod": "4.5.4",
|
|
32
34
|
"@vercel/connect": "^2.4.1",
|
|
33
35
|
"@vercel/oidc": "4.0.0"
|
package/roster-model.mjs
CHANGED
|
@@ -17,6 +17,14 @@ import { CONTEXT_WINDOW, selectModel } from "./bot-model.mjs";
|
|
|
17
17
|
import { DEFAULT_SUBAGENT_CAP } from "./subagent-cap.mjs";
|
|
18
18
|
import { sessionLimits } from "./session-limits.mjs";
|
|
19
19
|
import { findWorkspaceRoot, loadWorkspace, parseWorkspace } from "./workspace.mjs";
|
|
20
|
+
import { createGateway } from "ai";
|
|
21
|
+
import { hubGatewayFetch } from "./model-credential.mjs";
|
|
22
|
+
import { registryBase } from "./registry-fetch.mjs";
|
|
23
|
+
|
|
24
|
+
// Hosted, a bot's model ids resolve through the owner's sign-in from the hub
|
|
25
|
+
// (model-credential.mjs); elsewhere through the default gateway.
|
|
26
|
+
if (process.env.HUB_TOKEN)
|
|
27
|
+
globalThis.AI_SDK_DEFAULT_PROVIDER = createGateway({ apiKey: "hub-owner-sign-in", fetch: hubGatewayFetch(`${registryBase().replace(/\/$/, "")}/api/model-credential`) });
|
|
20
28
|
|
|
21
29
|
const real = process.env.ROSTER_MODELS !== "mock";
|
|
22
30
|
|
package/workspace-roster.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// A workspace bot's view of its roster (hub.workspace.json), for modules that
|
|
2
2
|
// need the agent list itself, e.g. a subagent resolving its capped model:
|
|
3
3
|
//
|
|
4
|
-
// import { agents, SUBAGENT_CAP } from "
|
|
5
|
-
// import { resolveSubagent } from "
|
|
4
|
+
// import { agents, SUBAGENT_CAP } from "eve-hub/workspace-roster";
|
|
5
|
+
// import { resolveSubagent } from "eve-hub/subagent-cap";
|
|
6
6
|
//
|
|
7
7
|
// Read once, at
|
|
8
8
|
// import (compile) time; see roster-model.mjs for how the file is found.
|