eve-hub 0.9.1 → 0.10.1
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 +12 -0
- package/README.md +2 -2
- package/execution-context.mjs +5 -5
- package/{templates/forms.md → forms/SKILL.md} +35 -4
- package/instructions/main.js +1 -1
- package/kit-modules.json +1 -0
- package/main-instructions.mjs +64 -23
- package/memory/team.d.ts +3 -3
- package/memory/team.js +15 -18
- package/package.json +2 -1
- package/prepare.mjs +14 -1
- package/role-files.mjs +5 -0
- package/schedules/hub.d.mts +1 -0
- package/schedules/hub.mjs +2 -0
- package/skills/forms.d.ts +2 -0
- package/skills/forms.gen.mjs +2 -0
- package/skills/forms.js +6 -0
- package/stubs.d.mts +3 -0
- package/stubs.mjs +37 -15
- package/sync-kit.mjs +42 -9
- package/team.d.mts +3 -4
- package/team.mjs +74 -59
- package/templates/deliveries.md +14 -0
- package/templates/engineering.md +26 -25
- package/templates/execution-context-hosted-main.md +0 -1
- package/templates/execution-context-local-main.md +0 -1
- package/templates/execution-context-local.md +0 -1
- package/templates/execution.md +31 -0
- package/templates/main-instructions.md +13 -32
- package/templates/reaching-others.md +18 -0
- package/templates/team.md +16 -0
- package/templates/writing.md +28 -0
- package/tools/attach_image.js +1 -1
- package/tools/show.js +1 -1
- package/templates/workspace-protocol.md +0 -160
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# eve-hub
|
|
2
2
|
|
|
3
|
+
## 0.10.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- The kit's hub client can store a Dash plan (`dashPlan`); the hub checks it and keeps it as the newest.
|
|
8
|
+
|
|
9
|
+
## 0.10.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Every bot's system prompt is rendered from kit templates, each in its own tag: identity, team, execution, engineering, what_reaches_you, reaching_others, writing, role, execution_context. A bot with `agents/<bot>/agent/role.md` (who it is in the first paragraph, then how it works) gets an `instructions.ts` stub that renders the whole prompt in that order; a bot with only `instructions.md` still builds, with the shared sections after it. The forms guide is the `forms` skill, every bot has it. The team text is replaced by `<team_architecture>`, which a bot builds from `hub.workspace.json` (a hosted bot's is embedded at build) and recalls once per session and when it changes; it no longer asks the hub. A bot's own additions to its role go in `agents/<bot>/agent/role/*.md`, inside `<role>`; `hub workspace sync` moves a kit-rendered bot's `instructions/*.md` there (eve would load that folder after the execution context, outside every tag), and reports a bot that has both `role.md` and `instructions.md` without changing it. `create_agent` writes `role.md`.
|
|
14
|
+
|
|
3
15
|
## 0.9.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@ The bot-side code every hub agent shares (milestone M3 in [`docs/architecture`](
|
|
|
7
7
|
| `tools/update_thread.ts`, `list_threads.ts`, `read_thread.ts`, `react.ts`, `show.ts`, `attach_image.ts`, `browser.ts` | Registry and workspace tools (`defineTool`) | `agent/tools/<same name>.ts` |
|
|
8
8
|
| `tools/create_thread.ts`, `post_message.ts`, `archive_thread.ts`, `unarchive_thread.ts`, `list_agents.ts`, `create_agent.ts`, `remove_agent.ts`, `restart_agent.ts`, `tag_threads.ts`, `apply_tags.ts` | The orchestrator's tools | `main`'s `agent/tools/<same name>.ts` |
|
|
9
9
|
| `hooks/sandbox.ts` | Migrates legacy Docker sessions to the host sandbox (`defineHook`) | `agent/hooks/sandbox.ts` |
|
|
10
|
-
| `instructions/main.ts`, `main-instructions.mjs` | main's instructions (`defineInstructions`), rendered at build time from the kit's `templates/main-instructions.md`
|
|
11
|
-
| `instructions/protocol.ts` | The shared
|
|
10
|
+
| `instructions/main.ts`, `main-instructions.mjs` | main's instructions (`defineInstructions`), rendered at build time from the kit's `templates/main-instructions.md` (identity, role) between the shared templates, each in its own tag, so no workspace carries a copy | `main`'s `agent/instructions.ts`; workspace-only additions in `agent/role/*.md` (inside `<role>`) |
|
|
11
|
+
| `instructions/protocol.ts` | The shared sections alone (`templates/team.md`, `execution.md`, `engineering.md`, `deliveries.md`, `reaching-others.md`, `writing.md`, each in its tag), for a bot whose `instructions.md` still says who it is and how it works | every other bot's `agent/instructions/protocol.ts`, appended after its own `instructions.md`; a bot with `agent/role.md` has `agent/instructions.ts` (`botInstructions`) instead, which renders identity, the shared sections and role in order |
|
|
12
12
|
| `instructions/engineering.ts` | The engineering page alone (`templates/engineering.md`), for a subagent that doesn't carry the protocol | the subagent's `instructions/engineering.ts` |
|
|
13
13
|
| `memory/workspace.ts`, `memory.mjs` | an eve memory slot (default export, constant `workspace` scope) whose provider recalls the workspace's `memory/<id>.md` entries (plus the `memory/<domain>/` folders the bot lists as `domains` in its roster entry), 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"` |
|
|
14
14
|
| `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"` |
|
package/execution-context.mjs
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
// instructions/execution-context.ts stub calls `executionContext`), or, for a
|
|
6
6
|
// bot the Vercel build deploys, a hosted page the build writes over that stub
|
|
7
7
|
// (prepare.mjs), because only the build knows the deployment. The shared
|
|
8
|
-
// prompts (templates
|
|
8
|
+
// prompts (templates/*.md) hold only
|
|
9
9
|
// what is true in both contexts.
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
|
-
import { templatesDir } from "./main-instructions.mjs";
|
|
12
|
+
import { section, templatesDir } from "./main-instructions.mjs";
|
|
13
13
|
import { agentRunsOn, parseWorkspace } from "./workspace.mjs";
|
|
14
14
|
|
|
15
15
|
const page = (name, options) => readFileSync(join(templatesDir(options), `execution-context-${name}.md`), "utf8").trimEnd();
|
|
@@ -42,7 +42,6 @@ const list = (items) => items.map((item) => `\`${item}\``).join(", ");
|
|
|
42
42
|
export function hostedContext({ bot, kit, removed, deployment: d }, options) {
|
|
43
43
|
const dashboard = d.team && d.project ? `https://vercel.com/${d.team}/${d.project}` : undefined;
|
|
44
44
|
const lines = [
|
|
45
|
-
"## Execution context",
|
|
46
45
|
`You are \`${bot}\`, an eve agent deployed on Vercel, not on a laptop: \`hub.workspace.json\` defines you as hosted.`,
|
|
47
46
|
"",
|
|
48
47
|
"- **Workspace:** one hosted hub holds the threads and routes messages to each bot. Who is on the team, where each bot runs and what each can do come live from the hub: the team model message at the top of your turn (when it changes) and `list_agents`.",
|
|
@@ -64,7 +63,7 @@ export function hostedContext({ bot, kit, removed, deployment: d }, options) {
|
|
|
64
63
|
`- **Logs:** ${dashboard ? `[runtime logs](${dashboard}/logs)${d.deployment ? ` and [this deployment](${dashboard}/${d.deployment.replace(/^dpl_/, "")})` : ""} in the Vercel dashboard` : "the Vercel project's runtime logs in the dashboard"}.`,
|
|
65
64
|
...(bot === "main" ? [page("hosted-main", options)] : []),
|
|
66
65
|
];
|
|
67
|
-
return `${lines.join("\n")}\n`;
|
|
66
|
+
return `${section("execution_context", lines.join("\n"))}\n`;
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
// What a bot's instructions/execution-context.ts stub renders. Hosted is the
|
|
@@ -75,5 +74,6 @@ export function executionContext(bot, workspace, options) {
|
|
|
75
74
|
if (runsOn !== "local") {
|
|
76
75
|
throw new Error(`${bot} is defined as hosted in hub.workspace.json, and a hosted bot's execution context is written by its Vercel build (eve-hub/vercel). A bot on this machine needs "runsOn": "local", on the agent or at the top of hub.workspace.json.`);
|
|
77
76
|
}
|
|
78
|
-
|
|
77
|
+
const lines = [`You are \`${bot}\`: \`hub.workspace.json\` defines you as local.`, "", page("local", options), ...(bot === "main" ? [page("local-main", options)] : [])];
|
|
78
|
+
return `${section("execution_context", lines.join("\n"))}\n`;
|
|
79
79
|
}
|
|
@@ -1,10 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: forms
|
|
3
|
+
description: How to show structure in a message. Load before you explain anything with parts that connect, events in order, numbers to compare, a before and after, or a choice the human must make; and before you post a table, diagram or chart.
|
|
4
|
+
---
|
|
1
5
|
|
|
2
|
-
|
|
6
|
+
# Showing structure
|
|
3
7
|
|
|
4
|
-
|
|
8
|
+
## Examples
|
|
9
|
+
|
|
10
|
+
The prompt's show-don't-tell rule says when to draw. These are past misses.
|
|
11
|
+
|
|
12
|
+
<example>
|
|
13
|
+
The human asks "how do we detect an existing workspace? is it stored in the hub db?" Miss: one sentence and numbered steps. Instead: the one-line answer, then a flow of today's sign-in check and, beside it, the flow with a "create new" option. The question never says "diagram"; it asks for a path through parts.
|
|
14
|
+
</example>
|
|
15
|
+
<example>
|
|
16
|
+
Build times by step. Miss: a schedule chart, then a paragraph repeating every step's seconds. Instead: the chart's title is the claim ("Linking is 70% of the build"), and the prose says only what to do about it.
|
|
17
|
+
</example>
|
|
18
|
+
<example>
|
|
19
|
+
Three candidate looks for a theme. Miss: a prose list of concepts. Instead: a rendered image of each, labeled in a few words.
|
|
20
|
+
</example>
|
|
21
|
+
|
|
22
|
+
## Picking the shape
|
|
23
|
+
- A path through three or more parts: `flow`.
|
|
24
|
+
- An order of events across two or more actors: `sequence`.
|
|
25
|
+
- Numbers to compare: `chart`. Time split into phases or hops (a latency breakdown, a turn's timeline) is a `schedule` chart, one row per request and one segment per phase, never one `bar` per total. A `bar` compares one number across items.
|
|
26
|
+
- A choice you need from the human: `ask`. Choosing an option sends its label back as their reply, so write labels you can act on.
|
|
27
|
+
- Two or more rows of short, comparable values: a table, at most four columns, a few words per cell. A single item, or anything that needs a reason, is a sentence or bullets.
|
|
28
|
+
- Anchor the picture in something the reader knows: a labeled before form, then an after form, in one message.
|
|
29
|
+
- Every part of one diagram is the same kind of thing (all hosts, all processes or all functions); say which in the sentence before it. A group is the level that contains them.
|
|
30
|
+
- At most one or two forms per message.
|
|
31
|
+
- Never draw with characters: no boxes, arrows, timelines or tables in a code fence.
|
|
32
|
+
|
|
33
|
+
## How the app shows it
|
|
34
|
+
- Each top-level code fence is a card with a copy button; a table is a table card; a link is a chip showing its label; an attached image is its own tile.
|
|
35
|
+
- A fenced form becomes an interactive figure the human can step through.
|
|
36
|
+
|
|
37
|
+
## Form syntax
|
|
5
38
|
|
|
6
|
-
- A code fence is for text the reader will paste, never for a picture: no boxes, arrows, timelines or tables drawn with characters.
|
|
7
|
-
- Pick the shape by what the numbers are. Time split into phases or hops (a latency breakdown, a turn's timeline) is a `schedule` chart: one row per turn or request, one start/end segment per phase, never one `bar` per total. A `bar` compares one number across items.
|
|
8
39
|
- Claim first: a sentence that says what the form shows comes before it. A chart's `title` is its claim.
|
|
9
40
|
- Link a word in your prose to a part with `[word](#id)`. Ids are lowercase slugs (`a-z`, `0-9`, `-`, up to 32 characters), unique in the whole message.
|
|
10
41
|
- Put the explanation of a part in `note` (up to 240 characters), which the human sees when they point at or step to it. Keep labels short.
|
package/instructions/main.js
CHANGED
|
@@ -2,5 +2,5 @@ import { defineInstructions } from "eve/instructions";
|
|
|
2
2
|
import { mainInstructions } from "../main-instructions.mjs";
|
|
3
3
|
// main's system prompt, from hub's templates (see main-instructions.mjs). A
|
|
4
4
|
// workspace's main re-exports this as agent/instructions.ts; workspace-only
|
|
5
|
-
// additions go in agent/
|
|
5
|
+
// additions go in agent/role/*.md, which the kit puts inside <role>.
|
|
6
6
|
export default defineInstructions({ content: mainInstructions() });
|
package/kit-modules.json
CHANGED
package/main-instructions.mjs
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
1
|
+
// A bot's system prompt, rendered from the kit's templates. Every section is wrapped in its own
|
|
2
|
+
// tag, in this order: identity, team, execution, engineering, what_reaches_you, reaching_others,
|
|
3
|
+
// writing, role, execution_context (the last one is execution-context.mjs). eve joins a bot's
|
|
4
|
+
// instruction sources with a blank line, flat instructions.{md,ts} first, then instructions/*
|
|
5
|
+
// by file name, so the order is made in code: one source renders identity, the shared sections
|
|
6
|
+
// and role together (botInstructions, mainInstructions), and instructions/execution-context.ts
|
|
7
|
+
// follows. One source for `hub workspace init` (lib/agent-template.mjs) and for every
|
|
8
|
+
// workspace's main, which imports it (instructions/main.ts) instead of carrying a copy, so a
|
|
9
|
+
// template edit reaches each workspace on its next build.
|
|
10
|
+
import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
9
11
|
import { dirname, join } from "node:path";
|
|
10
12
|
import { fileURLToPath } from "node:url";
|
|
11
|
-
import {
|
|
13
|
+
import { ROLE_DIR, ROLE_FILE } from "./role-files.mjs";
|
|
14
|
+
import { WorkspaceError, findWorkspaceRoot } from "./workspace.mjs";
|
|
12
15
|
|
|
13
16
|
const HEAD = "main-instructions.md";
|
|
14
|
-
const PROTOCOL = "workspace-protocol.md";
|
|
15
|
-
const FORMS = "forms.md";
|
|
16
17
|
const ENGINEERING = "engineering.md";
|
|
18
|
+
const SHARED = [
|
|
19
|
+
["team", "team.md"],
|
|
20
|
+
["execution", "execution.md"],
|
|
21
|
+
[ENGINEERING.slice(0, -3), ENGINEERING],
|
|
22
|
+
["what_reaches_you", "deliveries.md"],
|
|
23
|
+
["reaching_others", "reaching-others.md"],
|
|
24
|
+
["writing", "writing.md"],
|
|
25
|
+
];
|
|
17
26
|
const templatesOf = (kitDir) => join(kitDir, "templates");
|
|
18
27
|
|
|
19
28
|
// The templates sit next to the kit in the hub checkout. Run by node, this
|
|
@@ -26,22 +35,54 @@ export function templatesDir({ env = process.env, cwd = process.cwd() } = {}) {
|
|
|
26
35
|
const link = join(findWorkspaceRoot({ env, cwd }), "node_modules", "eve-hub");
|
|
27
36
|
const linked = templatesOf(realpathSync(link));
|
|
28
37
|
if (existsSync(join(linked, HEAD))) return linked;
|
|
29
|
-
throw new Error(`
|
|
38
|
+
throw new Error(`the kit's prompt templates aren't at ${here} or ${linked} (through ${link})`);
|
|
30
39
|
}
|
|
31
40
|
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
export const section = (tag, body) => `<${tag}>\n${body.trim()}\n</${tag}>`;
|
|
42
|
+
const template = (name, options) => readFileSync(join(templatesDir(options), name), "utf8");
|
|
43
|
+
|
|
44
|
+
// A role file is who the bot is (its first paragraph), then how it works.
|
|
45
|
+
export function splitRole(text, source = "role file") {
|
|
46
|
+
const [identity, ...rest] = text.trim().split(/\n\s*\n/);
|
|
47
|
+
const role = rest.join("\n\n").trim();
|
|
48
|
+
if (!identity || !role) throw new Error(`${source} needs an identity paragraph, a blank line, then the role`);
|
|
49
|
+
return { identity, role };
|
|
35
50
|
}
|
|
36
51
|
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
// What eve joins: each source's content with a blank line between, and trims only the whole. So a
|
|
53
|
+
// source ends at its closing tag, with no newline of its own.
|
|
54
|
+
const assemble = ({ identity, role }, additions, options) =>
|
|
55
|
+
[section("identity", identity), workspaceProtocol(options), section("role", [role, ...additions].join("\n\n"))].join("\n\n");
|
|
56
|
+
|
|
57
|
+
// A bot's own additions to its role: agents/<bot>/agent/role/*.md, by file name, inside <role>.
|
|
58
|
+
// They can't sit in instructions/*.md: eve loads that folder itself, after execution-context.ts,
|
|
59
|
+
// outside every tag. A workspace with no folder (or no workspace at all) has none.
|
|
60
|
+
function additionsOf(bot, options) {
|
|
61
|
+
let root;
|
|
62
|
+
try {
|
|
63
|
+
root = findWorkspaceRoot(options);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof WorkspaceError) return [];
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
const dir = join(root, "agents", bot, "agent", ROLE_DIR);
|
|
69
|
+
if (!existsSync(dir)) return [];
|
|
70
|
+
return readdirSync(dir).filter((f) => f.endsWith(".md")).sort().map((f) => readFileSync(join(dir, f), "utf8").trim());
|
|
41
71
|
}
|
|
42
72
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
73
|
+
export const mainInstructions = (options) => assemble(splitRole(template(HEAD, options), HEAD), additionsOf("main", options), options);
|
|
74
|
+
|
|
75
|
+
// Every other bot: agents/<bot>/agent/role.md in the workspace.
|
|
76
|
+
export { ROLE_DIR, ROLE_FILE };
|
|
77
|
+
export function botInstructions(bot, options) {
|
|
78
|
+
const file = join(findWorkspaceRoot(options), "agents", bot, "agent", ROLE_FILE);
|
|
79
|
+
return assemble(splitRole(readFileSync(file, "utf8"), file), additionsOf(bot, options), options);
|
|
47
80
|
}
|
|
81
|
+
|
|
82
|
+
// The engineering section alone, for a subagent that doesn't carry the shared sections
|
|
83
|
+
// (instructions/engineering.ts).
|
|
84
|
+
export const engineeringPage = (options) => `${section("engineering", template(ENGINEERING, options))}\n`;
|
|
85
|
+
|
|
86
|
+
// The shared sections, with no identity or role: what a bot whose instructions.md still holds
|
|
87
|
+
// both gets appended (instructions/protocol.ts).
|
|
88
|
+
export const workspaceProtocol = (options) => SHARED.map(([tag, file]) => section(tag, template(file, options))).join("\n\n");
|
package/memory/team.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
declare const recall: ({ messages }: {
|
|
2
2
|
messages: readonly unknown[];
|
|
3
|
-
}) =>
|
|
3
|
+
}) => {
|
|
4
4
|
messages: {
|
|
5
5
|
id: string;
|
|
6
6
|
content: string;
|
|
7
7
|
}[];
|
|
8
|
-
}
|
|
8
|
+
};
|
|
9
9
|
declare const _default: import("eve/memory").DefinedMemory<{
|
|
10
|
-
readonly description: "The team:
|
|
10
|
+
readonly description: "The team: how work flows between roles, what each owns, where bots run, and your place.";
|
|
11
11
|
readonly namespace: "hub-team-memory";
|
|
12
12
|
readonly scope: "workspace";
|
|
13
13
|
readonly provider: {
|
package/memory/team.js
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
1
2
|
import { defineMemory, defineMemoryProvider } from "eve/memory";
|
|
2
|
-
import {
|
|
3
|
-
import { teamRecall } from "../team.mjs";
|
|
4
|
-
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
3
|
+
import { findBotDir } from "../memory.mjs";
|
|
4
|
+
import { architectureText, teamRecall } from "../team.mjs";
|
|
5
|
+
import { findWorkspaceRoot, readWorkspaceFile } from "../workspace.mjs";
|
|
6
|
+
// The team architecture (team.mjs) as an eve memory slot: how work flows between roles, what each
|
|
7
|
+
// role owns, where bots run and this bot's own place, built from the workspace's hub.workspace.json
|
|
8
|
+
// (a hosted bot's is embedded by its Vercel build, prepare.mjs). Recall runs at the start of every
|
|
9
|
+
// turn and after compaction, and adds one message only when the text differs from what this
|
|
10
|
+
// session was last given. The bot is the agents/<bot> directory this process runs from (findBotDir).
|
|
9
11
|
const warned = new Set();
|
|
10
|
-
|
|
11
|
-
const res = await registryFetch(`/api/team${since ? `?since=${since}` : ""}`, { signal: AbortSignal.timeout(5_000) });
|
|
12
|
-
if (res.status === 304)
|
|
13
|
-
return null;
|
|
14
|
-
if (!res.ok)
|
|
15
|
-
throw new Error(`GET /api/team: HTTP ${res.status}: ${(await res.text()).slice(0, 200)}`);
|
|
16
|
-
return (await res.json());
|
|
17
|
-
}
|
|
18
|
-
const recall = async ({ messages }) => {
|
|
12
|
+
const recall = ({ messages }) => {
|
|
19
13
|
try {
|
|
20
|
-
|
|
14
|
+
const dir = findBotDir();
|
|
15
|
+
if (!dir)
|
|
16
|
+
return { messages: [] };
|
|
17
|
+
return teamRecall(messages, architectureText(readWorkspaceFile(findWorkspaceRoot()).data, basename(dir)));
|
|
21
18
|
}
|
|
22
19
|
catch (error) {
|
|
23
20
|
const note = error.message;
|
|
@@ -29,7 +26,7 @@ const recall = async ({ messages }) => {
|
|
|
29
26
|
}
|
|
30
27
|
};
|
|
31
28
|
export default defineMemory({
|
|
32
|
-
description: "The team:
|
|
29
|
+
description: "The team: how work flows between roles, what each owns, where bots run, and your place.",
|
|
33
30
|
namespace: "hub-team-memory",
|
|
34
31
|
scope: "workspace",
|
|
35
32
|
provider: defineMemoryProvider({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eve-hub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
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",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"./roster-model": "./roster-model.mjs",
|
|
19
19
|
"./workspace": "./workspace.mjs",
|
|
20
20
|
"./memory": "./memory.mjs",
|
|
21
|
+
"./team": "./team.mjs",
|
|
21
22
|
"./workspace-roster": "./workspace-roster.mjs",
|
|
22
23
|
"./bot-model": "./bot-model.mjs",
|
|
23
24
|
"./subagent-cap": "./subagent-cap.mjs",
|
package/prepare.mjs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
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 hub's
|
|
13
13
|
// workflow world is wired in (and the Vercel Kit connection), every bot gets eve's git extension, and the
|
|
14
|
-
// workspace's memory
|
|
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
|
|
17
17
|
// from, for the hub to sync from: a custom channel on main, routed in
|
|
@@ -25,6 +25,7 @@ export { DEPLOYED_KIT_FILES };
|
|
|
25
25
|
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
|
+
import { architectureText } from "./team.mjs";
|
|
28
29
|
|
|
29
30
|
// eve's git extension, for every bot: it points the bot at `gh` and `git` in
|
|
30
31
|
// bash, whose GitHub credential is the sandbox firewall's (the Vercel sandbox lib/agent-template.mjs writes).
|
|
@@ -97,6 +98,15 @@ const recall = ({ messages }) => ({ messages: memoryRecall(entries, messages, {
|
|
|
97
98
|
export default defineMemory({ namespace: ${JSON.stringify(`hub-${slot}-memory`)}, scope: ${JSON.stringify(scope)}, provider: defineMemoryProvider({ recall: { "turn.started": recall, "compaction.completed": recall } }) });
|
|
98
99
|
`;
|
|
99
100
|
|
|
101
|
+
// The team slot of a hosted bot: its architecture text, built from the whole roster (Mac bots
|
|
102
|
+
// included) at build time, since a function has no workspace file to read.
|
|
103
|
+
const teamProvider = (text) => `import { defineMemory, defineMemoryProvider } from "eve/memory";
|
|
104
|
+
import { teamRecall } from "eve-hub/team";
|
|
105
|
+
const text = ${JSON.stringify(text)};
|
|
106
|
+
const recall = ({ messages }) => teamRecall(messages, text);
|
|
107
|
+
export default defineMemory({ namespace: "hub-team-memory", scope: "workspace", provider: defineMemoryProvider({ recall: { "turn.started": recall, "compaction.completed": recall } }) });
|
|
108
|
+
`;
|
|
109
|
+
|
|
100
110
|
// Replaces the kit's execution-context stub (the local page) with the hosted
|
|
101
111
|
// page, for a bot the definition says is hosted. Its removed-tools list is read
|
|
102
112
|
// from the bot's folder as pruned.
|
|
@@ -158,6 +168,9 @@ export function prepareDeployment(dir) {
|
|
|
158
168
|
mkdirSync(join(bot, "memory"), { recursive: true });
|
|
159
169
|
const botDir = join(dir, "agents", name);
|
|
160
170
|
writeFileSync(join(bot, "memory", "workspace.ts"), memoryProvider("workspace", readMemory(dir, { folders: domainFolders(botDomains(dir, botDir)) }).entries, "Standing decision", "workspace"));
|
|
171
|
+
// A folder the roster doesn't list has no place in the team, and a function can't read the file the stub would.
|
|
172
|
+
if (definition.agents.some((a) => a.name === name)) writeFileSync(join(bot, "memory", "team.ts"), teamProvider(architectureText(definition.data, name)));
|
|
173
|
+
else rmSync(join(bot, "memory", "team.ts"), { force: true });
|
|
161
174
|
writeFileSync(join(bot, "memory", "bot.ts"), memoryProvider("bot", readBotMemory(dir, botDir).entries, "Your standing decision", name));
|
|
162
175
|
}
|
|
163
176
|
}
|
package/role-files.mjs
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Where a bot's role lives in agents/<bot>/agent/: role.md (who it is, then how it works) and
|
|
2
|
+
// role/*.md (its own additions to the role). Shared by the sync (stubs.mjs) and the renderer
|
|
3
|
+
// (main-instructions.mjs), which a deployed bot imports without stubs.mjs.
|
|
4
|
+
export const ROLE_FILE = "role.md";
|
|
5
|
+
export const ROLE_DIR = "role";
|
package/schedules/hub.d.mts
CHANGED
|
@@ -18,6 +18,7 @@ export type Hub = {
|
|
|
18
18
|
setSummary(path: string, text: string): Promise<unknown>;
|
|
19
19
|
dashClassified(): Promise<Ok & { classifiedAt: string }>;
|
|
20
20
|
fileThread(threadId: string, path: string): Promise<unknown>;
|
|
21
|
+
dashPlan(basis: string, plan: object): Promise<Ok & { id: string }>;
|
|
21
22
|
dashEntries(entries: object[], remove?: string[], notice?: Notice | null): Promise<Ok & { entries: DashEntries }>;
|
|
22
23
|
};
|
|
23
24
|
export function createHub(options?: { base?: string; agent?: string; fetchImpl?: typeof fetch }): Hub;
|
package/schedules/hub.mjs
CHANGED
|
@@ -72,6 +72,8 @@ export function createHub({ base = registryBase(), agent = "main", fetchImpl = f
|
|
|
72
72
|
// File an unfiled thread into a track (the classifier's pick), as a bot:
|
|
73
73
|
// the hub refuses a thread that's already filed and doesn't lock it.
|
|
74
74
|
fileThread: (threadId, path) => must(`/api/threads/${encodeURIComponent(threadId)}/track`, { method: "POST", body: JSON.stringify({ by: agent, path }) }),
|
|
75
|
+
// The plan main's pass made (wire DashPlanIn, checked in hosted-hub/src/domain/dash.mjs normalizePlan); the hub keeps it as the newest.
|
|
76
|
+
dashPlan: (basis, plan) => must("/api/dash/plan", { method: "POST", body: JSON.stringify({ by: agent, basis, plan }) }),
|
|
75
77
|
// `notice` ({ schedule, text, clientId? }): posted in that schedule's
|
|
76
78
|
// thread in the same write as the entries, or neither is kept.
|
|
77
79
|
dashEntries: (entries, remove = [], notice = null) => must("/api/dash/entries", { method: "POST", body: JSON.stringify({ by: agent, entries, remove, ...(notice ? { notice } : {}) }) }),
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// Generated by scripts/build-onboarding.mjs from forms/SKILL.md. Do not edit.
|
|
2
|
+
export default {"description":"How to show structure in a message. Load before you explain anything with parts that connect, events in order, numbers to compare, a before and after, or a choice the human must make; and before you post a table, diagram or chart.","markdown":"# Showing structure\n\n## Examples\n\nThe prompt's show-don't-tell rule says when to draw. These are past misses.\n\n<example>\nThe human asks \"how do we detect an existing workspace? is it stored in the hub db?\" Miss: one sentence and numbered steps. Instead: the one-line answer, then a flow of today's sign-in check and, beside it, the flow with a \"create new\" option. The question never says \"diagram\"; it asks for a path through parts.\n</example>\n<example>\nBuild times by step. Miss: a schedule chart, then a paragraph repeating every step's seconds. Instead: the chart's title is the claim (\"Linking is 70% of the build\"), and the prose says only what to do about it.\n</example>\n<example>\nThree candidate looks for a theme. Miss: a prose list of concepts. Instead: a rendered image of each, labeled in a few words.\n</example>\n\n## Picking the shape\n- A path through three or more parts: `flow`.\n- An order of events across two or more actors: `sequence`.\n- Numbers to compare: `chart`. Time split into phases or hops (a latency breakdown, a turn's timeline) is a `schedule` chart, one row per request and one segment per phase, never one `bar` per total. A `bar` compares one number across items.\n- A choice you need from the human: `ask`. Choosing an option sends its label back as their reply, so write labels you can act on.\n- Two or more rows of short, comparable values: a table, at most four columns, a few words per cell. A single item, or anything that needs a reason, is a sentence or bullets.\n- Anchor the picture in something the reader knows: a labeled before form, then an after form, in one message.\n- Every part of one diagram is the same kind of thing (all hosts, all processes or all functions); say which in the sentence before it. A group is the level that contains them.\n- At most one or two forms per message.\n- Never draw with characters: no boxes, arrows, timelines or tables in a code fence.\n\n## How the app shows it\n- Each top-level code fence is a card with a copy button; a table is a table card; a link is a chip showing its label; an attached image is its own tile.\n- A fenced form becomes an interactive figure the human can step through.\n\n## Form syntax\n\n- Claim first: a sentence that says what the form shows comes before it. A chart's `title` is its claim.\n- Link a word in your prose to a part with `[word](#id)`. Ids are lowercase slugs (`a-z`, `0-9`, `-`, up to 32 characters), unique in the whole message.\n- Put the explanation of a part in `note` (up to 240 characters), which the human sees when they point at or step to it. Keep labels short.\n- `hi: true` marks the one part that matters, at most one per form. Unknown keys are rejected.\n- Send a form with `post_message`, never in your end-of-turn reply. The hub checks a posted form and rejects a bad one (400) with what's wrong: fix it and post again. Nothing checks a final reply, so a bad form there shows as raw JSON.\n\nA request leaves the [app](#app), the [hub](#hub) routes it, and a [bot](#bot) answers. `flow`: nodes 2–12 (`role`, `group`, `ref` as file:line), edges 1–24 (`from`, `to`, `label` up to 16, `fail` for a failure path), optional `groups`. Steps follow node order.\n\n```flow\n{\"nodes\": [\n {\"id\": \"app\", \"label\": \"App\", \"role\": \"on your Mac\"},\n {\"id\": \"hub\", \"label\": \"Hub\", \"role\": \"threads, routing\", \"hi\": true, \"ref\": \"server.mjs:5323\", \"note\": \"Stores the message, then wakes the bot.\"},\n {\"id\": \"bot\", \"label\": \"Bot\", \"role\": \"one turn\"}],\n \"edges\": [{\"from\": \"app\", \"to\": \"hub\", \"label\": \"send\"}, {\"from\": \"hub\", \"to\": \"bot\", \"label\": \"wake\"}, {\"from\": \"bot\", \"to\": \"hub\", \"label\": \"post\"}]}\n```\n\nThe stream [drops](#drop) before the client [retries](#retry). `sequence`: actors 2–6, messages 1–16 (`from`, `to`, `label` up to 32, `fail` where it breaks, `ref` as file:line; `from` = `to` for a wait or retry inside one actor). Optional `unit` (up to 48 characters) is printed above the actors, e.g. \"each line = one process\"; optional `groups` (up to 3, `id` and `label`) with `group` on an actor draws a box around actors that share a host; grouped actors must sit next to each other. Steps follow message order.\n\n```sequence\n{\"unit\": \"each line = one process\", \"groups\": [{\"id\": \"mac\", \"label\": \"Mac\"}, {\"id\": \"hub-proj\", \"label\": \"Hub project\"}],\n \"actors\": [{\"id\": \"client\", \"label\": \"Client\", \"group\": \"mac\"}, {\"id\": \"hub\", \"label\": \"Hub\", \"group\": \"hub-proj\"}],\n \"messages\": [\n {\"id\": \"drop\", \"from\": \"hub\", \"to\": \"client\", \"label\": \"stream drops\", \"fail\": true, \"ref\": \"Client.swift:257\"},\n {\"id\": \"retry\", \"from\": \"client\", \"to\": \"hub\", \"label\": \"reconnects\", \"note\": \"Resumes from the last event id.\"}]}\n```\n\nFirst token is under 2 s everywhere but [Sydney](#syd). `chart`: `title` (the claim) and `points` 1–24, each with an id and `label`. `type` is one of four:\n- `bar`: `value`; optional `unit`, `target`.\n- `scatter`: axis titles `x`, `y`; points have numeric `x`, `y`.\n- `budget`: `value` > 0; optional `total`; `spare: true` for unused room.\n- `schedule`: `row` (up to 8 rows), `start` < `end`; optional `unit`.\n\n```chart\n{\"type\": \"bar\", \"title\": \"First token is under 2 s everywhere but Sydney\", \"unit\": \"s\", \"target\": 2,\n \"points\": [{\"id\": \"sfo\", \"label\": \"San Francisco\", \"value\": 0.9}, {\"id\": \"syd\", \"label\": \"Sydney\", \"value\": 2.6, \"hi\": true, \"note\": \"No region near it.\"}]}\n```\n\nYou need a decision, so I'm asking you to pick one. `ask`: 2–5 `options`, each with `id`, `label` (up to 40) and an optional one-line `line` (up to 160). Choosing one sends its label back as the human's reply, so write labels you can act on.\n\n```ask\n{\"options\": [\n {\"id\": \"heron\", \"label\": \"Heron Lodge\", \"line\": \"Lake house 90 minutes north. Rooms for all 14, $5,400.\"},\n {\"id\": \"pines\", \"label\": \"The Pines\", \"line\": \"In town, walkable. Sleeps 10, so 4 need a hotel.\"}]}\n```\n"};
|
package/skills/forms.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { defineSkill } from "eve/skills";
|
|
2
|
+
import skill from "./forms.gen.mjs";
|
|
3
|
+
// The forms skill: forms/SKILL.md, embedded at build time (scripts/build-onboarding.mjs) so it
|
|
4
|
+
// loads the same on the hub's machine and in a deployed bot. The prompt's "show, don't tell"
|
|
5
|
+
// rule says when to draw; the skill says how.
|
|
6
|
+
export default defineSkill(skill);
|
package/stubs.d.mts
CHANGED
|
@@ -5,3 +5,6 @@ export const MEMORY_STUBS: Record<string, string>;
|
|
|
5
5
|
export const PROTOCOL_STUB_FILE: string;
|
|
6
6
|
export const PROTOCOL_STUB: string;
|
|
7
7
|
export function kitStub(file: string, options?: { files?: Record<string, string[]> }): string;
|
|
8
|
+
export const ROLE_FILE: string;
|
|
9
|
+
export const ROLE_DIR: string;
|
|
10
|
+
export function botInstructionsStub(name: string): string;
|
package/stubs.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ROLE_DIR, ROLE_FILE } from "./role-files.mjs";
|
|
2
|
+
|
|
1
3
|
// eve finds tools, hooks, and channels by file (the tool name is the file
|
|
2
4
|
// name), so each bot keeps a one-line stub per kit module. Bot file -> what it
|
|
3
5
|
// is, and which kit module it re-exports (default: the same path).
|
|
@@ -15,6 +17,7 @@ export const KIT_FILES = {
|
|
|
15
17
|
"tools/read_thread.ts": ["Read any workspace thread."],
|
|
16
18
|
"tools/list_agents.ts": ["The team right now: kind, place, tools, models, who's busy."],
|
|
17
19
|
"tools/post_message.ts": ["Post into an existing thread or a bot's DM; with `status`, report the thread's phase."],
|
|
20
|
+
"skills/forms.ts": ["The forms skill (packages/agent-kit/forms/SKILL.md): how to draw a flow, sequence, chart or ask the app renders."],
|
|
18
21
|
};
|
|
19
22
|
|
|
20
23
|
// main, the orchestrator: the shared files (the same channel as every bot),
|
|
@@ -56,6 +59,7 @@ export const DEPLOYED_KIT_FILES = new Set([
|
|
|
56
59
|
"tools/onboarding_scripts.ts",
|
|
57
60
|
...["react", "show", "update_thread", "list_threads", "read_thread", "post_message"].map((t) => `tools/${t}.ts`),
|
|
58
61
|
"memory/team.ts",
|
|
62
|
+
"skills/forms.ts",
|
|
59
63
|
...["create_thread", "set_summary", "move_path", "archive_thread", "unarchive_thread", "list_agents", "create_agent", "remove_agent", "restart_agent", "tag_threads", "dash_update", "apply_tags"].map((t) => `tools/${t}.ts`),
|
|
60
64
|
]);
|
|
61
65
|
|
|
@@ -67,17 +71,30 @@ export function kitStub(file, { files = KIT_FILES, kit = files[file]?.[1] ?? fil
|
|
|
67
71
|
return `// ${what} From eve-hub (packages/agent-kit/${kit}).\nexport { default } from "${kitPath(kit)}";\n`;
|
|
68
72
|
}
|
|
69
73
|
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
// and is never copied into a bot.
|
|
74
|
+
// A bot whose instructions.md still holds both who it is and how it works: instructions/protocol.ts
|
|
75
|
+
// re-exports the kit's shared sections (packages/agent-kit/templates/, each in its own tag). eve
|
|
76
|
+
// appends the instructions/ entries after instructions.md, so they follow it and are never copied
|
|
77
|
+
// into a bot. A bot with a role.md (below) gets the whole prompt in order instead.
|
|
75
78
|
export const PROTOCOL_STUB_FILE = "instructions/protocol.ts";
|
|
76
|
-
export const PROTOCOL_STUB = `// The
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
+
export const PROTOCOL_STUB = `// The shared sections every bot carries, from packages/agent-kit/templates/, through eve-hub
|
|
80
|
+
// (packages/agent-kit/instructions/protocol.ts). eve appends them after instructions.md.
|
|
81
|
+
// Don't copy them there.
|
|
79
82
|
export { default } from "eve-hub/instructions/protocol";
|
|
80
83
|
`;
|
|
84
|
+
// A bot with a role.md (agents/<bot>/agent/role.md: who it is in the first paragraph, then how it
|
|
85
|
+
// works): instructions.ts renders its whole prompt, in order: identity, the shared sections, role
|
|
86
|
+
// (packages/agent-kit/main-instructions.mjs). eve puts instructions.ts before instructions/*, so
|
|
87
|
+
// the execution context stub follows.
|
|
88
|
+
export { ROLE_DIR, ROLE_FILE };
|
|
89
|
+
export const botInstructionsStub = (name) => `import { defineInstructions } from "eve/instructions";
|
|
90
|
+
import { botInstructions } from "eve-hub/main-instructions";
|
|
91
|
+
|
|
92
|
+
// ${name}'s system prompt: its role.md between the kit's templates (packages/agent-kit/templates/).
|
|
93
|
+
// Edit who ${name} is and how it works in role.md, not here.
|
|
94
|
+
export default defineInstructions({ content: botInstructions(${JSON.stringify(name)}) });
|
|
95
|
+
`;
|
|
96
|
+
const BOT_STUB = /^import \{ defineInstructions \} from "eve\/instructions";\s*import \{ botInstructions \} from "eve-hub\/main-instructions";\s*export default defineInstructions\(\{ content: botInstructions\("[a-z][a-z0-9-]*"\) \}\);?$/;
|
|
97
|
+
|
|
81
98
|
// Every bot (main too): instructions/execution-context.ts renders the bot's
|
|
82
99
|
// execution context from its definition in hub.workspace.json
|
|
83
100
|
// (packages/agent-kit/execution-context.mjs). A hosted bot's is replaced by
|
|
@@ -107,13 +124,13 @@ export const MEMORY_STUBS = {
|
|
|
107
124
|
};
|
|
108
125
|
|
|
109
126
|
// main's instructions aren't copied into a workspace: agent/instructions.ts
|
|
110
|
-
// re-exports the kit's, which renders packages/agent-kit/templates/main-instructions.md
|
|
111
|
-
//
|
|
112
|
-
// Workspace-only additions go in agent/
|
|
127
|
+
// re-exports the kit's, which renders packages/agent-kit/templates/main-instructions.md between
|
|
128
|
+
// the shared templates at build time (packages/agent-kit/main-instructions.mjs).
|
|
129
|
+
// Workspace-only additions go in agent/role/*.md, inside <role>.
|
|
113
130
|
export const MAIN_INSTRUCTIONS = "instructions.ts";
|
|
114
|
-
export const MAIN_INSTRUCTIONS_STUB = `// main's instructions, from packages/agent-kit/templates (main-instructions.md +
|
|
131
|
+
export const MAIN_INSTRUCTIONS_STUB = `// main's instructions, from packages/agent-kit/templates (main-instructions.md + the shared sections),
|
|
115
132
|
// through eve-hub (packages/agent-kit/instructions/main.ts). Don't copy them here:
|
|
116
|
-
// workspace-only additions go in
|
|
133
|
+
// workspace-only additions go in role/*.md next to this file, inside <role>.
|
|
117
134
|
export { default } from "eve-hub/instructions/main";
|
|
118
135
|
`;
|
|
119
136
|
|
|
@@ -129,8 +146,8 @@ export function isKitReexport(source, module = "instructions/main") {
|
|
|
129
146
|
|
|
130
147
|
|
|
131
148
|
// Where a bot's instructions come from, given its files ({ "instructions.md": text, ... }):
|
|
132
|
-
// main ("kit", "copy", "custom", "conflict", "missing"), or any other bot (the
|
|
133
|
-
// "kit", "copy", "conflict", "custom", "missing").
|
|
149
|
+
// main ("kit", "copy", "custom", "conflict", "missing"), or any other bot (the shared sections:
|
|
150
|
+
// "kit", "copy", "conflict", "custom", "missing"; "kit" also for a role.md with its instructions.ts stub).
|
|
134
151
|
const PROTOCOL_HEADING = /^## How this workspace works\s*$/m;
|
|
135
152
|
export function instructionsSource(files, main) {
|
|
136
153
|
const has = (file) => typeof files[file] === "string";
|
|
@@ -139,6 +156,11 @@ export function instructionsSource(files, main) {
|
|
|
139
156
|
if (has(MAIN_INSTRUCTIONS)) return isKitReexport(files[MAIN_INSTRUCTIONS]) ? "kit" : "custom";
|
|
140
157
|
return has("instructions.md") ? "copy" : "missing";
|
|
141
158
|
}
|
|
159
|
+
if (has(MAIN_INSTRUCTIONS)) {
|
|
160
|
+
if (has("instructions.md")) return "conflict";
|
|
161
|
+
const code = files[MAIN_INSTRUCTIONS].replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").trim();
|
|
162
|
+
return BOT_STUB.test(code) ? "kit" : "custom";
|
|
163
|
+
}
|
|
142
164
|
const copy = has("instructions.md") && PROTOCOL_HEADING.test(files["instructions.md"]);
|
|
143
165
|
if (!has(PROTOCOL_STUB_FILE)) return copy ? "copy" : "missing";
|
|
144
166
|
if (copy) return "conflict";
|