eve-hub 0.16.3 → 0.16.4
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 +1 -1
- package/execution-context.mjs +6 -5
- package/local-onboarding/SKILL.md +2 -2
- package/local-onboarding/prepare-edit.mjs +24 -9
- package/package.json +2 -2
- package/prepare.mjs +52 -13
- package/sandbox-migrate.d.mts +2 -0
- package/sandbox-migrate.mjs +39 -0
- package/skills/local-onboarding.gen.mjs +1 -1
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
|
eve-hub's version is `package.json`'s `version` (the base) plus one patch per `.changeset/*.md` naming `eve-hub` (`lib/kit-version.mjs`). A PR that changes the kit only adds its changeset (`pnpm changeset`), so two open kit PRs never edit the same file. When it merges, `ci.yml`'s `deploy-hub` job runs `scripts/publish.mjs`, which stages the package into `dist/npm` with the computed version, publishes it when npm lacks it, and the `eve-hub@<version>` GitHub release is created; nothing is committed (main's ruleset allows only PR merges). A minor or major release raises the base and deletes the pending changesets in one PR; a kit change must raise the version, and it never goes down (`test/kit-version-bumped.test.mjs`). CHANGELOG.md stops at 0.14.x; the release notes and changesets carry later changes. 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)
|
|
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). Every hosted bot's sandbox (lib/agent-template.mjs `sandboxFile`, written at `create_agent`) installs eve-code's tooling (`installCodeTooling`: `gh` and `gh-signed-commit` under `/usr/local/lib/eve-code`), sets no network policy and holds no credential. The template's builder also mounts eve-code (`eve/extensions/code`: `apply_patch`, `grep`, the review skills, a read-only worker) with no `github` option. Every bot that edits files, in a build from a workspace repo, mounts `eve/extensions/git` as `extensions/git.ts` with a `github` option: connector `github/eve-hub-github` (the hub's GitHub App, which `attachHub` links to the bots project for every environment at onboarding), `principalType: "user"` (each `git__gh` command runs with the human's own GitHub token, which Vercel Connect resolves through that App; every hub turn, wake-ups and bot handoffs included, runs as the workspace's human, so pushes, PRs and commits are theirs; the first command raises a sign-in card), `authorize` limiting commands to the repo owner's org (`VERCEL_GIT_REPO_OWNER`), and a broker that puts the token into the Vercel Sandbox's firewall (`setNetworkPolicy`) for that command alone. The bot runs `git`, `gh` and `gh-signed-commit` through `git__gh`; plain `git` and `gh` in bash have no credential. A read-only bot (eve's `write_file` disabled) gets none, since the user token can't be narrowed below the human's own write access; a bot's own committed `extensions/git.ts` is kept as the workspace's. A build without a repo (an uploaded deployment) mounts no git extension, removes one the tree carries (the legacy `extensions/git/extension.ts` stub too), and the execution context points the bot to the `vercel-kit` connection instead (packages/agent-kit/prepare.mjs, execution-context.mjs; the rendered prompts are snapshotted in hosted-hub/test/prompts). 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 `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. A hosted bot's `sandbox.ts` from an earlier template (the `GITHUB_TOKEN` policy, no eve-code tooling) is moved by the hosted kit update (`sandbox-migrate.mjs`, from `hosted-hub/src/onboard/repo-bump.ts` `kitChange`), keeping the bot's own `prepare` steps; a build from a repo refuses a hosted sandbox `git__gh` can't run in. 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/execution-context.mjs
CHANGED
|
@@ -22,7 +22,8 @@ const claims = (jwt) => JSON.parse(Buffer.from(jwt.split(".")[1], "base64url").t
|
|
|
22
22
|
|
|
23
23
|
export function deploymentFacts(env = process.env) {
|
|
24
24
|
const oidc = env.VERCEL_OIDC_TOKEN ? claims(env.VERCEL_OIDC_TOKEN) : {};
|
|
25
|
-
const
|
|
25
|
+
const repoOwner = env.VERCEL_GIT_REPO_OWNER && env.VERCEL_GIT_REPO_SLUG ? env.VERCEL_GIT_REPO_OWNER : undefined;
|
|
26
|
+
const repo = repoOwner ? `${repoOwner}/${env.VERCEL_GIT_REPO_SLUG}` : undefined;
|
|
26
27
|
return {
|
|
27
28
|
team: oidc.owner,
|
|
28
29
|
project: oidc.project,
|
|
@@ -31,15 +32,15 @@ export function deploymentFacts(env = process.env) {
|
|
|
31
32
|
url: env.VERCEL_URL,
|
|
32
33
|
environment: env.VERCEL_ENV,
|
|
33
34
|
repo,
|
|
35
|
+
repoOwner,
|
|
34
36
|
branch: env.VERCEL_GIT_COMMIT_REF,
|
|
35
37
|
sha: env.VERCEL_GIT_COMMIT_SHA,
|
|
36
|
-
githubToken: Boolean(env.GITHUB_TOKEN),
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
const list = (items) => items.map((item) => `\`${item}\``).join(", ");
|
|
41
42
|
|
|
42
|
-
export function hostedContext({ bot, kit, removed, deployment: d }, options) {
|
|
43
|
+
export function hostedContext({ bot, kit, removed, deployment: d, git = false }, options) {
|
|
43
44
|
const dashboard = d.team && d.project ? `https://vercel.com/${d.team}/${d.project}` : undefined;
|
|
44
45
|
const lines = [
|
|
45
46
|
`You are \`${bot}\`, an eve agent deployed on Vercel, not on a laptop: \`hub.workspace.json\` defines you as hosted.`,
|
|
@@ -57,8 +58,8 @@ export function hostedContext({ bot, kit, removed, deployment: d }, options) {
|
|
|
57
58
|
d.repo
|
|
58
59
|
? "- **Changing it:** commit to that repository; Vercel builds and deploys the commit."
|
|
59
60
|
: "- **Changing it:** there is no repository to commit to. The hub replaces these files on its next deploy, so an edit made only in this deployment does not last.",
|
|
60
|
-
|
|
61
|
-
? "- **GitHub:** `
|
|
61
|
+
git
|
|
62
|
+
? "- **GitHub:** goes through the `git__gh` tool (`git`, `gh`, `gh-signed-commit`), which runs each command with the human's own GitHub token, swapped in at the sandbox firewall for that command, so pushes, pull requests and commits are theirs. The shell itself holds no credential, so plain `git` and `gh` in bash cannot authenticate."
|
|
62
63
|
: "- **GitHub:** your shell has no GitHub credential: `git` cannot authenticate, so a private repository won't clone and nothing can be pushed. When a `vercel-kit` connection is listed, reach GitHub through it (eve__search, then eve__tool); otherwise tell the human this deployment has no GitHub access.",
|
|
63
64
|
"- **Memory:** the standing decisions you recall are the `memory/` and `agents/<bot>/memory/` files, built into each deployment. An entry takes effect when the deployment that contains it is live, not on your next turn; with no repository it cannot last.",
|
|
64
65
|
"- **Limits:** there is no `hub` CLI, you cannot restart yourself or any bot, and there are no host paths: your shell is a Vercel Sandbox with its own filesystem, not the owner's machine.",
|
|
@@ -62,7 +62,7 @@ Mapping each fact to a setup is your judgment, not a lookup table. Decide, for e
|
|
|
62
62
|
- `localhost` services and the keychain.
|
|
63
63
|
|
|
64
64
|
Everything else can be hosted.
|
|
65
|
-
- **Already there, or add it.** A hosted bot's sandbox is eve's Ubuntu image with git
|
|
65
|
+
- **Already there, or add it.** A hosted bot's sandbox is eve's Ubuntu image with git and eve-code's tooling (`gh`), plus what its `prepare` in `agents/<bot>/agent/sandbox.ts` installs. Read `prepare` in the workspace repo's sandbox files. A program it installs is already provided; any other hosted program needs a `prepare` step. Use `apt-get` where Ubuntu has the package, and the project's official installer otherwise.
|
|
66
66
|
|
|
67
67
|
MCP servers are reached from hosted bots through Vercel Kit connections: list each one by name for main to connect. Env var names are listed for the owner only. Never print a value (you don't have any). Ignore shell and system names (`PATH`, `HOME`, `CI`, ...). A name with a high `shellAssigned` relative to `count` is probably a shell variable, not part of the environment.
|
|
68
68
|
|
|
@@ -84,6 +84,6 @@ Main's handoff names the workspace repo. Clone it into a scratch folder, branch,
|
|
|
84
84
|
node {{scripts}}/prepare-edit.mjs "$TMPDIR/prepare.json" agents/builder/agent/sandbox.ts
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
Commit, push and open the PR with `gh pr create
|
|
87
|
+
Commit, push and open the PR with `gh pr create` (a hosted bot runs `git` and `gh` through its `git__gh` tool). The body lists the programs, why each one is there, and the usage counts behind them. Never name the owner's other repos.
|
|
88
88
|
|
|
89
89
|
The PR's Vercel build runs `prepare`, so a step that fails, fails the build. Wait for its result with `gh pr checks --watch --fail-fast`. If it fails, fix the step in `prepare.json`, restore the file from the branch you cut from (`git checkout origin/HEAD -- <file>`), rerun the script and push. Post the PR link and its build result in the thread, then delete the scratch clone.
|
|
@@ -2,24 +2,39 @@
|
|
|
2
2
|
// Adds the sandbox setup local-onboarding proposed to hosted bots' sandbox.ts, in place:
|
|
3
3
|
// node prepare-edit.mjs proposal.json agents/<bot>/agent/sandbox.ts [...]
|
|
4
4
|
// proposal.json is {"commands": [{"command": "<shell>", "what": "<short name for the error>"}]}, the one source of
|
|
5
|
-
// the steps: they are written as `await run(...)` calls into `prepare`,
|
|
6
|
-
// session's snapshot has them.
|
|
7
|
-
// `run` helper
|
|
5
|
+
// the steps: they are written as `await run(...)` calls into `prepare`, after eve-code's tooling install, so every
|
|
6
|
+
// new session's snapshot has them. The scaffold's one-line `prepare` (lib/agent-template.mjs) is opened into a block
|
|
7
|
+
// with the `run` helper the first time; a step already in the file is not added twice. A sandbox.ts with neither
|
|
8
|
+
// form is refused: edit that one by hand. One an earlier template wrote is moved first (sandbox-migrate.mjs), so no
|
|
9
|
+
// step lands after its `if (!token) return;`.
|
|
8
10
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
9
11
|
import { pathToFileURL } from 'node:url';
|
|
12
|
+
import { migrateSandbox } from '../sandbox-migrate.mjs';
|
|
10
13
|
|
|
14
|
+
const ONE_LINE = 'VercelSandbox.environment({ prepare: (sandbox) => installCodeTooling(sandbox) });\n';
|
|
15
|
+
const OPENED = `VercelSandbox.environment({ prepare: async (sandbox) => {
|
|
16
|
+
await installCodeTooling(sandbox);
|
|
17
|
+
const run = async (command, what) => {
|
|
18
|
+
if ((await sandbox.run({ command })).exitCode !== 0) throw new Error(what + " failed");
|
|
19
|
+
};
|
|
20
|
+
// The programs the owner's coding agents use (local-onboarding).
|
|
21
|
+
} });
|
|
22
|
+
`;
|
|
11
23
|
const RUN = /^ const run = async \(command, what\) => \{$/m;
|
|
12
|
-
const
|
|
24
|
+
const END = '} });\n';
|
|
13
25
|
|
|
14
26
|
export function addPrepareSteps(source, commands) {
|
|
15
27
|
if (!Array.isArray(commands) || !commands.every((c) => typeof c?.command === 'string' && c.command.trim() && typeof c?.what === 'string' && c.what.trim())) {
|
|
16
28
|
throw new Error('commands must be [{ command, what }] with both non-empty');
|
|
17
29
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
30
|
+
const moved = migrateSandbox(source);
|
|
31
|
+
if (moved.reason) throw new Error(`this sandbox.ts ${moved.reason}; add the steps by hand`);
|
|
32
|
+
const current = moved.source;
|
|
33
|
+
const opened = current.includes(ONE_LINE) ? current.replace(ONE_LINE, () => OPENED) : current;
|
|
34
|
+
if (!RUN.test(opened) || !opened.includes(END)) throw new Error("this sandbox.ts isn't the scaffold's (no `prepare` to open or `run` helper): add the steps by hand");
|
|
35
|
+
const lines = commands.map(({ command, what }) => ` await run(${JSON.stringify(command)}, ${JSON.stringify(what)});\n`).filter((line) => !opened.includes(line));
|
|
36
|
+
if (!lines.length) return current;
|
|
37
|
+
return opened.replace(END, () => `${lines.join('')}${END}`);
|
|
23
38
|
}
|
|
24
39
|
|
|
25
40
|
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eve-hub",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.4",
|
|
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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@vercel/connect": "^2.4.1",
|
|
37
37
|
"@vercel/oidc": "4.0.0",
|
|
38
|
-
"eve": "
|
|
38
|
+
"eve": "~0.88.0",
|
|
39
39
|
"zod": "4.5.4"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
package/prepare.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// off the hub's machine (DEPLOYED_KIT_FILES); a hosted bot whose sandbox.ts
|
|
11
11
|
// is the host's fails the build, and the build never writes a sandbox;
|
|
12
12
|
// - the kit extension, host-only workers and kit stubs are dropped, the
|
|
13
|
-
// Vercel Kit connection is wired in, every bot gets eve's git extension, and the
|
|
13
|
+
// Vercel Kit connection is wired in, every bot of a build from a repo gets eve's git extension, and the
|
|
14
14
|
// workspace's memory and each bot's team architecture are embedded in the bots' memory providers (a function
|
|
15
15
|
// has no workspace cwd);
|
|
16
16
|
// - /hub/manifest: the roster and the commit sha the deployment was built
|
|
@@ -26,14 +26,47 @@ import { deploymentFacts, hostedContext } from "./execution-context.mjs";
|
|
|
26
26
|
import { botDomains, domainFolders, readBotMemory, readMemory, validateMemory } from "./memory.mjs";
|
|
27
27
|
import { agentRunsOn, readWorkspaceFile, removeWorkspaceAgent } from "./workspace.mjs";
|
|
28
28
|
import { architectureText } from "./team.mjs";
|
|
29
|
+
import { sandboxReady } from "./sandbox-migrate.mjs";
|
|
29
30
|
|
|
30
|
-
// eve's git extension, for
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
31
|
+
// eve's git extension, for each bot of a build from a workspace repo that edits files: its `git__gh` tool runs
|
|
32
|
+
// git, gh and gh-signed-commit against the workspace's org with the human's own GitHub token,
|
|
33
|
+
// resolved by Vercel Connect through the GITHUB_CONNECTOR app (the hub links the connector to the
|
|
34
|
+
// bots project at onboarding) and swapped in at the sandbox firewall for that command alone. Every
|
|
35
|
+
// hub turn runs as the workspace's human (channels/eve-roster-user.ts), so pushes, PRs and commits
|
|
36
|
+
// are theirs. The shell holds no credential. A build without a repo (an uploaded deployment)
|
|
37
|
+
// mounts none.
|
|
38
|
+
export const GITHUB_CONNECTOR = "github/eve-hub-github";
|
|
39
|
+
export const GIT_EXTENSION_FILE = "extensions/git.ts";
|
|
40
|
+
// The read-only preset's stub (lib/agent-template.mjs): eve's write_file, disabled.
|
|
41
|
+
const READ_ONLY_FILE = "tools/write_file.ts";
|
|
42
|
+
// The stub an earlier kit wrote, with no `github` option; two git extensions would collide.
|
|
43
|
+
export const LEGACY_GIT_EXTENSION_FILE = "extensions/git/extension.ts";
|
|
44
|
+
export const gitExtensionFiles = (org) => ({ [GIT_EXTENSION_FILE]: `import git from "eve/extensions/git";
|
|
45
|
+
import type { GitHubLeaseBroker } from "eve/extensions/git";
|
|
46
|
+
import type { SandboxNetworkPolicy } from "eve/sandbox";
|
|
47
|
+
import type { VercelSandboxSession } from "eve/sandbox/vercel";
|
|
48
|
+
|
|
49
|
+
// Each \`git__gh\` command runs with the caller's own GitHub token, resolved by Vercel Connect
|
|
50
|
+
// through the eve-hub-github App, so pushes, PRs and commits are the human's. Every hub turn,
|
|
51
|
+
// wake-ups and bot handoffs included, runs as the workspace's human (channels/eve-roster-user).
|
|
52
|
+
// The sandbox only sees a placeholder; this swaps the real token in at the sandbox firewall for
|
|
53
|
+
// that command and removes it afterwards. All other egress stays open.
|
|
54
|
+
const broker: GitHubLeaseBroker = async (sandbox, rules) => {
|
|
55
|
+
if (!("setNetworkPolicy" in sandbox)) throw new Error("GitHub credential leases need a Vercel Sandbox");
|
|
56
|
+
await (sandbox as VercelSandboxSession).setNetworkPolicy({ allow: { "*": [], ...rules } } as SandboxNetworkPolicy);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const ORG = ${JSON.stringify(org.toLowerCase())};
|
|
60
|
+
|
|
61
|
+
export default git({
|
|
62
|
+
github: {
|
|
63
|
+
connector: ${JSON.stringify(GITHUB_CONNECTOR)},
|
|
64
|
+
broker,
|
|
65
|
+
authorize: ({ repository }) => repository.toLowerCase().startsWith(\`\${ORG}/\`),
|
|
66
|
+
principalType: "user",
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
` });
|
|
37
70
|
|
|
38
71
|
// Bots that edit code: eve-code (apply_patch, grep, the review skills and its
|
|
39
72
|
// read-only worker), with no `github` option. The worker runs on the roster's
|
|
@@ -109,7 +142,7 @@ export default defineMemory({ namespace: "hub-team-memory", scope: "workspace",
|
|
|
109
142
|
function writeExecutionContext(bot, { bot: name, deployment }) {
|
|
110
143
|
const kitTools = Object.keys(name === "main" ? MAIN_KIT_FILES : KIT_FILES).filter((file) => file.startsWith("tools/") && file !== "tools/bash.ts").map((file) => basename(file, ".ts"));
|
|
111
144
|
const removed = kitTools.filter((tool) => !existsSync(join(bot, "tools", `${tool}.ts`)));
|
|
112
|
-
const section = hostedContext({ bot: name, kit: KIT_VERSION, removed, deployment });
|
|
145
|
+
const section = hostedContext({ bot: name, kit: KIT_VERSION, removed, deployment, git: existsSync(join(bot, GIT_EXTENSION_FILE)) });
|
|
113
146
|
mkdirSync(join(bot, "instructions"), { recursive: true });
|
|
114
147
|
writeFileSync(join(bot, "instructions", "execution-context.ts"), `import { defineInstructions } from "eve/instructions";\n\nexport default defineInstructions({ content: ${JSON.stringify(section)} });\n`);
|
|
115
148
|
}
|
|
@@ -128,6 +161,10 @@ export function prepareDeployment(dir) {
|
|
|
128
161
|
const tiersOf = (name) => Object.keys(definition.agents.find((a) => a.name === name)?.subagents ?? {});
|
|
129
162
|
const change = syncKit({ bots: folders.map((name) => ({ name, runsOn: "hosted", files: readBotFiles(dirOf(name)), subagents: tiersOf(name) })), modules: KIT_MODULES });
|
|
130
163
|
if (change.report.length) throw new Error(`these files can't ship to a hosted deployment; update or delete them:\n${change.report.map((r) => ` agents/${r.bot}/agent/${r.file} ${r.reason}`).join("\n")}`);
|
|
164
|
+
const deployment = deploymentFacts();
|
|
165
|
+
// The git extension's `git__gh` runs eve-code's gh; a sandbox an earlier template wrote has none. The hub's kit update moves it (sandbox-migrate.mjs).
|
|
166
|
+
const unready = deployment.repoOwner ? folders.filter((name) => existsSync(join(dirOf(name), "sandbox.ts")) && !sandboxReady(readFileSync(join(dirOf(name), "sandbox.ts"), "utf8"))) : [];
|
|
167
|
+
if (unready.length) throw new Error(`these sandboxes can't run the git__gh tool: add \`await installCodeTooling(sandbox);\` (from eve/extensions/code/sandbox) to prepare and remove the GITHUB_TOKEN policy, or let the hub's workspace update move them:\n${unready.map((name) => ` agents/${name}/agent/sandbox.ts`).join("\n")}`);
|
|
131
168
|
writeFileSync(join(dir, MANIFEST_FILE), `${JSON.stringify(resolved, null, 2)}\n`);
|
|
132
169
|
for (const { name } of definition.agents) {
|
|
133
170
|
if (agentRunsOn(definition, name) !== "local") continue;
|
|
@@ -137,18 +174,20 @@ export function prepareDeployment(dir) {
|
|
|
137
174
|
mkdirSync(join(dir, "agents", "main", "agent", "channels"), { recursive: true });
|
|
138
175
|
writeFileSync(join(dir, "agents", "main", "agent", "channels", "manifest.ts"), MANIFEST_CHANNEL);
|
|
139
176
|
applyKit(change, dirOf);
|
|
140
|
-
const deployment = deploymentFacts();
|
|
141
177
|
// Bots only: a declared subagent's agent.ts (agent/subagents/<tier>/) is not a bot and mounts none of these.
|
|
142
178
|
for (const name of readdirSync(join(dir, "agents"))) {
|
|
143
179
|
const bot = dirOf(name);
|
|
144
180
|
if (!existsSync(join(bot, "agent.ts"))) continue;
|
|
145
181
|
const code = CODE_BOTS.includes(name);
|
|
146
|
-
|
|
182
|
+
// A user token carries all of the human's write access and can't be narrowed, so a read-only bot
|
|
183
|
+
// gets none. A bot's own committed git.ts (a wider org list, say) is the workspace's and stays.
|
|
184
|
+
const git = deployment.repoOwner && !existsSync(join(bot, READ_ONLY_FILE)) && !existsSync(join(bot, GIT_EXTENSION_FILE));
|
|
185
|
+
for (const [stub, content] of Object.entries({ ...(git ? gitExtensionFiles(deployment.repoOwner) : {}), ...(code ? codeExtensionFiles(name) : {}) })) {
|
|
147
186
|
mkdirSync(dirname(join(bot, stub)), { recursive: true });
|
|
148
187
|
writeFileSync(join(bot, stub), content);
|
|
149
188
|
}
|
|
150
|
-
|
|
151
|
-
if (!deployment.
|
|
189
|
+
if (existsSync(join(bot, LEGACY_GIT_EXTENSION_FILE))) removeStub(bot, LEGACY_GIT_EXTENSION_FILE);
|
|
190
|
+
if (!deployment.repoOwner && existsSync(join(bot, GIT_EXTENSION_FILE))) removeStub(bot, GIT_EXTENSION_FILE);
|
|
152
191
|
}
|
|
153
192
|
for (const name of readdirSync(join(dir, "agents"))) {
|
|
154
193
|
const bot = join(dir, "agents", name, "agent");
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Moves a hosted bot's sandbox.ts, as an earlier template wrote it, to the current one: eve-git's `git__gh` tool
|
|
2
|
+
// (prepare.mjs mounts it on every hosted bot) runs the `gh` and `gh-signed-commit` that eve-code's
|
|
3
|
+
// `installCodeTooling` installs, and its GitHub token comes per command, so the TEMPORARY `GITHUB_TOKEN` firewall
|
|
4
|
+
// policy and the token-only prepare steps go. Steps a person or local-onboarding added stay where they are: the
|
|
5
|
+
// earlier template put them before `if (!token) return;`. Only the template's own lines after it are removed; a
|
|
6
|
+
// sandbox with anything else there is refused, for a person to edit.
|
|
7
|
+
// The hosted kit bump (hosted-hub/src/onboard/repo-bump.ts) commits what this returns; the build (prepare.mjs)
|
|
8
|
+
// refuses a hosted sandbox.ts this hasn't moved.
|
|
9
|
+
|
|
10
|
+
const TOOLING_IMPORT = 'import { installCodeTooling } from "eve/extensions/code/sandbox";\n';
|
|
11
|
+
const VERCEL_IMPORT = 'import { VercelSandbox } from "eve/sandbox/vercel";\n';
|
|
12
|
+
const TOOLING_CALL = /\binstallCodeTooling\(sandbox\)/;
|
|
13
|
+
// From the TEMPORARY comment through the policy's end, and the blank line after it.
|
|
14
|
+
const TOKEN_POLICY = /^\/\/ TEMPORARY \(until the Vercel Connect GitHub connector replaces it\)[\s\S]*?^\} \} : undefined;\n\n?/m;
|
|
15
|
+
// The token line and the token-only steps after it (apt gh, placeholder login, signing key), exactly as the template
|
|
16
|
+
// wrote them, up to the end of `prepare`. With any other line among or after them the token line stays, and the
|
|
17
|
+
// sandbox is refused rather than losing that line.
|
|
18
|
+
const TOKEN_STEPS = " if (!token) return;\n // eve's sandbox image is Ubuntu with git only; gh and ssh-keygen come from apt.\n await run(\"sudo apt-get update -qq && sudo apt-get install -y -qq gh openssh-client\", \"gh and ssh installation\");\n // gh refuses to run without a login; this one is a placeholder the firewall overwrites.\n await run(\"mkdir -p ~/.config/gh && printf 'github.com:\\n user: hub\\n oauth_token: placeholder\\n git_protocol: https\\n' > ~/.config/gh/hosts.yml\", \"gh login\");\n // The org requires signed commits. Eve reuses a named template sandbox after a failed prepare, so\n // this step starts by clearing an earlier key. A key made here, registered as a signing key on\n // the token's user, never leaves the sandbox; commits carry that user's noreply email.\n await run([\n \"mkdir -p ~/.ssh && rm -f ~/.ssh/signing ~/.ssh/signing.pub && ssh-keygen -q -t ed25519 -N '' -C hub-bot -f ~/.ssh/signing\",\n \"gh api user/ssh_signing_keys -f title=\\\"hub bot sandbox $(date -u +%FT%TZ)\\\" -f key=\\\"$(cat ~/.ssh/signing.pub)\\\" --silent\",\n \"login=$(gh api user --jq .login) && id=$(gh api user --jq .id)\",\n \"git config --global user.name \\\"$login\\\" && git config --global user.email \\\"$id+$login@users.noreply.github.com\\\"\",\n \"git config --global gpg.format ssh && git config --global user.signingkey ~/.ssh/signing && git config --global commit.gpgsign true\",\n ].join(\" && \"), \"git signing setup\");\n";
|
|
19
|
+
const PREPARE_END = "} });\n";
|
|
20
|
+
const PREPARE_OPEN = "VercelSandbox.environment({ prepare: async (sandbox) => {\n";
|
|
21
|
+
const LEFTOVER = /\bGITHUB_TOKEN\b|\bnetworkPolicy\b|\bif \(!token\)/;
|
|
22
|
+
|
|
23
|
+
/** Whether a hosted sandbox.ts is one `git__gh` can run in: it installs eve-code's tooling and keeps no `GITHUB_TOKEN` policy. */
|
|
24
|
+
export const sandboxReady = (source) => TOOLING_CALL.test(source) && !LEFTOVER.test(source.replace(/\/\/.*$/gm, ""));
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `{ source }`: the sandbox.ts moved (the same text when it is ready already), or `{ reason }` when it isn't a form
|
|
28
|
+
* this knows, for a person to edit by hand.
|
|
29
|
+
*/
|
|
30
|
+
export function migrateSandbox(source) {
|
|
31
|
+
if (sandboxReady(source)) return { source };
|
|
32
|
+
let out = source.replace(TOKEN_POLICY, "").replace(TOKEN_STEPS + PREPARE_END, () => PREPARE_END).replace("VercelSandbox.environment({ networkPolicy, prepare:", "VercelSandbox.environment({ prepare:");
|
|
33
|
+
if (!TOOLING_CALL.test(out)) {
|
|
34
|
+
if (!out.includes(PREPARE_OPEN) || !out.includes(VERCEL_IMPORT)) return { reason: "doesn't install eve-code's tooling, which the git__gh tool needs: add `await installCodeTooling(sandbox);` (from eve/extensions/code/sandbox) to its prepare" };
|
|
35
|
+
out = out.replace(VERCEL_IMPORT, () => VERCEL_IMPORT + TOOLING_IMPORT).replace(PREPARE_OPEN, () => `${PREPARE_OPEN} await installCodeTooling(sandbox);\n`);
|
|
36
|
+
}
|
|
37
|
+
if (!sandboxReady(out)) return { reason: "still uses GITHUB_TOKEN, a networkPolicy or steps after `if (!token) return;` that the template didn't write, which the git__gh tool replaces: move those steps before that line or remove them, then remove the token policy" };
|
|
38
|
+
return { source: out };
|
|
39
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by scripts/build-onboarding.mjs from local-onboarding/SKILL.md. Do not edit.
|
|
2
|
-
export default {"description":"The Mac half of onboarding, when main hands it to you: find the owner's live coding-agent work (Claude Code, Codex, OpenCode, Cursor) to port into threads, and propose the sandbox setup their hosted bots need. Reads only; the threads and the sandbox PR wait for the owner's yes.","content":"# The Mac half of onboarding\n\nMain runs onboarding on Vercel, where it can't see this Mac, so it hands you this half and posts its own without waiting. You post two short messages in that thread, the live work first, then the sandbox setup. Follow your writing rules: each first bubble is at most two sentences, with tables below a `---` line.\n\nTwo scripts gather the facts. They live in `{{scripts}}`, read only, and print metadata: repos, branches, counts, times, the agents' own session titles and program names. Work only from what they print. Don't open the agents' session files yourself: they hold the owner's prompts and code. Delete the JSON files when you are done.\n\n## 1. Live work to port\n\n```sh\nnode {{scripts}}/sessions-facts.mjs > \"$TMPDIR/sessions.json\"\n```\n\nThe script covers the last 14 days of sessions, grouped into work-streams by repo and branch, newest first. Each stream has:\n- `repo`, `branch` and `pr` (the open PR);\n- `checkouts`: one per clone or worktree it was worked in. Each has `worktree` (true for a linked git worktree), `uncommitted` (changed files) and `unpushed` (commits ahead of `aheadOf`);\n- `lastActive`;\n- `sessions`: each with its agent, title, time and `checkout` (its index in `checkouts`).\n\nA stream with no remote is keyed `local-N`. A stream marked `gone` is a folder that no longer exists. `unreadable` lists the sources that exist but couldn't be read. An `uncommitted` or `unpushed` of `null` means the count couldn't be determined (git timed out on a huge untracked tree, or there is no upstream or default branch to compare against): it is unknown, not zero.\n\nPost the streams that have work in flight: any checkout with uncommitted files or unpushed commits, or an open PR. A checkout with an unknown count gets a row too, with \"unknown\" in that cell. A stream with more than one checkout gets one row per checkout that has work. Put the rest in one line with their count.\n\n```md\nYou have 3 work-streams in flight on this Mac. Say which to port, and main will open a thread for each so the team can pick it up.\n\n---\n\n| Stream | Agent and session | Last active | Uncommitted | Unpushed | PR |\n|---|---|---|---|---|---|\n| `vercel/app` on `fix-login` (worktree) | Claude Code, \"Fix login redirect\" | 2h ago | 4 files | 2 | [#812](...) |\n\nAlso 5 streams with nothing in flight.\n```\n\nNever post a local path, a file name, a prompt, or anything else from the sessions.\n\nWhen the owner says which streams to port, you still write nothing on the Mac. Post one message that mentions main and asks it to open one thread per stream. For each stream, give a short title, the repo and branch, its state (worktree, uncommitted and unpushed counts, PR), and which agent had it. You don't open threads yourself.\n\n## 2. Sandbox setup\n\n```sh\nnode {{scripts}}/agent-usage-facts.mjs > \"$TMPDIR/agent-usage.json\"\n```\n\nIt takes about 30 seconds on 16 GB of history. It prints names and counts, never a command's arguments, a prompt or a value:\n- `scanned`: records, commands and unparsable commands per agent;\n- `unreadable`;\n- `programs`, `mcpServers`, `repos`, `languages` (file extensions) and `envNames`.\n\nMapping each fact to a setup is your judgment, not a lookup table. Decide, for every program in the top 30:\n\n- **Hosted or Mac.** These can't run in a Linux sandbox, so they stay on the Mac with you:\n - `brew`, `osascript` and `open`;\n - Xcode, `xcodebuild` and Apple-platform `swift`;\n - anything that drives the owner's browser or apps;\n - `localhost` services and the keychain.\n\n Everything else can be hosted.\n- **Already there, or add it.** A hosted bot's sandbox is eve's Ubuntu image with git
|
|
2
|
+
export default {"description":"The Mac half of onboarding, when main hands it to you: find the owner's live coding-agent work (Claude Code, Codex, OpenCode, Cursor) to port into threads, and propose the sandbox setup their hosted bots need. Reads only; the threads and the sandbox PR wait for the owner's yes.","content":"# The Mac half of onboarding\n\nMain runs onboarding on Vercel, where it can't see this Mac, so it hands you this half and posts its own without waiting. You post two short messages in that thread, the live work first, then the sandbox setup. Follow your writing rules: each first bubble is at most two sentences, with tables below a `---` line.\n\nTwo scripts gather the facts. They live in `{{scripts}}`, read only, and print metadata: repos, branches, counts, times, the agents' own session titles and program names. Work only from what they print. Don't open the agents' session files yourself: they hold the owner's prompts and code. Delete the JSON files when you are done.\n\n## 1. Live work to port\n\n```sh\nnode {{scripts}}/sessions-facts.mjs > \"$TMPDIR/sessions.json\"\n```\n\nThe script covers the last 14 days of sessions, grouped into work-streams by repo and branch, newest first. Each stream has:\n- `repo`, `branch` and `pr` (the open PR);\n- `checkouts`: one per clone or worktree it was worked in. Each has `worktree` (true for a linked git worktree), `uncommitted` (changed files) and `unpushed` (commits ahead of `aheadOf`);\n- `lastActive`;\n- `sessions`: each with its agent, title, time and `checkout` (its index in `checkouts`).\n\nA stream with no remote is keyed `local-N`. A stream marked `gone` is a folder that no longer exists. `unreadable` lists the sources that exist but couldn't be read. An `uncommitted` or `unpushed` of `null` means the count couldn't be determined (git timed out on a huge untracked tree, or there is no upstream or default branch to compare against): it is unknown, not zero.\n\nPost the streams that have work in flight: any checkout with uncommitted files or unpushed commits, or an open PR. A checkout with an unknown count gets a row too, with \"unknown\" in that cell. A stream with more than one checkout gets one row per checkout that has work. Put the rest in one line with their count.\n\n```md\nYou have 3 work-streams in flight on this Mac. Say which to port, and main will open a thread for each so the team can pick it up.\n\n---\n\n| Stream | Agent and session | Last active | Uncommitted | Unpushed | PR |\n|---|---|---|---|---|---|\n| `vercel/app` on `fix-login` (worktree) | Claude Code, \"Fix login redirect\" | 2h ago | 4 files | 2 | [#812](...) |\n\nAlso 5 streams with nothing in flight.\n```\n\nNever post a local path, a file name, a prompt, or anything else from the sessions.\n\nWhen the owner says which streams to port, you still write nothing on the Mac. Post one message that mentions main and asks it to open one thread per stream. For each stream, give a short title, the repo and branch, its state (worktree, uncommitted and unpushed counts, PR), and which agent had it. You don't open threads yourself.\n\n## 2. Sandbox setup\n\n```sh\nnode {{scripts}}/agent-usage-facts.mjs > \"$TMPDIR/agent-usage.json\"\n```\n\nIt takes about 30 seconds on 16 GB of history. It prints names and counts, never a command's arguments, a prompt or a value:\n- `scanned`: records, commands and unparsable commands per agent;\n- `unreadable`;\n- `programs`, `mcpServers`, `repos`, `languages` (file extensions) and `envNames`.\n\nMapping each fact to a setup is your judgment, not a lookup table. Decide, for every program in the top 30:\n\n- **Hosted or Mac.** These can't run in a Linux sandbox, so they stay on the Mac with you:\n - `brew`, `osascript` and `open`;\n - Xcode, `xcodebuild` and Apple-platform `swift`;\n - anything that drives the owner's browser or apps;\n - `localhost` services and the keychain.\n\n Everything else can be hosted.\n- **Already there, or add it.** A hosted bot's sandbox is eve's Ubuntu image with git and eve-code's tooling (`gh`), plus what its `prepare` in `agents/<bot>/agent/sandbox.ts` installs. Read `prepare` in the workspace repo's sandbox files. A program it installs is already provided; any other hosted program needs a `prepare` step. Use `apt-get` where Ubuntu has the package, and the project's official installer otherwise.\n\nMCP servers are reached from hosted bots through Vercel Kit connections: list each one by name for main to connect. Env var names are listed for the owner only. Never print a value (you don't have any). Ignore shell and system names (`PATH`, `HOME`, `CI`, ...). A name with a high `shellAssigned` relative to `count` is probably a shell variable, not part of the environment.\n\nPost the proposal:\n- a first bubble on what the hosted bots would gain;\n- a table of the steps to add, with the programs each installs and how often the owner used them;\n- one line for what stays on the Mac;\n- one line for the MCP servers to connect through Kit.\n\nAsk whether to open the PR.\n\n## 3. The sandbox PR, after the owner's yes\n\nThe steps live in one file, which is the only source; never hand-write them into `sandbox.ts`. Write `$TMPDIR/prepare.json` as `{\"commands\": [{\"command\": \"<shell>\", \"what\": \"<short name for the error>\"}]}`.\n\nMain's handoff names the workspace repo. Clone it into a scratch folder, branch, and add the steps to each hosted bot that runs code. That is the builder, unless the owner said otherwise.\n\n```sh\nnode {{scripts}}/prepare-edit.mjs \"$TMPDIR/prepare.json\" agents/builder/agent/sandbox.ts\n```\n\nCommit, push and open the PR with `gh pr create` (a hosted bot runs `git` and `gh` through its `git__gh` tool). The body lists the programs, why each one is there, and the usage counts behind them. Never name the owner's other repos.\n\nThe PR's Vercel build runs `prepare`, so a step that fails, fails the build. Wait for its result with `gh pr checks --watch --fail-fast`. If it fails, fix the step in `prepare.json`, restore the file from the branch you cut from (`git checkout origin/HEAD -- <file>`), rerun the script and push. Post the PR link and its build result in the thread, then delete the scratch clone.\n"};
|