eve-hub 0.16.4 → 0.16.6
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/package.json +1 -1
- package/templates/writing.md +26 -4
- package/tools/post_message.d.ts +2 -0
- package/tools/post_message.js +48 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# eve-hub
|
|
2
2
|
|
|
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.
|
|
3
|
+
The bot-side code every hub agent shares (milestone M3 in [`docs/architecture`](../../docs/architecture/history/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
|
|
|
5
5
|
| Module | What it is | Bots use it as |
|
|
6
6
|
|---|---|---|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eve-hub",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.6",
|
|
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",
|
package/templates/writing.md
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
- **The reader**
|
|
2
2
|
- Your messages land in a chat feed, like Slack or iMessage. The human runs many threads and gives each a few minutes, so every message is read cold, often only its first line.
|
|
3
3
|
- The reader has none of your context.
|
|
4
|
-
- **
|
|
4
|
+
- **Who these rules are for**
|
|
5
|
+
- A post that @-mentions no bot is one the human reads: a result, a status report, a question. Every rule below applies to it.
|
|
6
|
+
- A post that @-mentions a bot is a handoff. The app folds bot-to-bot chatter, so a handoff keeps the dense format `<team_architecture>` asks for, ids and shas included. Only the length cap and the layout rules apply to it.
|
|
7
|
+
- **Sound like a person** (core principle)
|
|
8
|
+
- Write in the first person, the way a colleague texts you: what you did, what you found, and what happens next. "I fixed the late-link bug and pushed it. The reviewer has it now."
|
|
9
|
+
- Write whole sentences, with their articles and verbs, one idea per sentence. No fragments ("Proven, in this sandbox:"), no arrows or symbol-speak, no labels standing in for sentences.
|
|
10
|
+
- Say what a thing does, or give the number, not how it feels. "Lock wait went from 716 ms to 0", not "much snappier".
|
|
11
|
+
- Use the plain word, and cut every word that does no work. Vary sentence length, and have a view: say what you recommend and why.
|
|
12
|
+
- Example. Instead of "**What changed:** each link's name is now a column on `subagent_links`. **Why one table stays:** …", write: "I took out the side table you asked about. One new table has to stay, because the app reads thread totals on every refresh, and adding up the raw steps there gets 23 times slower once a thread has 400 steps. The fix is pushed and back with the reviewer."
|
|
13
|
+
- **Shorter never means less complete**
|
|
14
|
+
- Keep every fact the reader needs: decisions, results, numbers, what's open, and what you need from them and why. Keep each next step as planned: "publishes hub-runner 0.2.1" never becomes "checks it's on npm".
|
|
15
|
+
- The codebase is the word list. Name a function, field, flag, endpoint, file or command by its exact name in code font (`commitRefs`, `post_message`, `/api/dash/entries`), never by a vague description of it. Keep every specific name your work found; "Dash writes" in place of "`/api/dash/entries`, plan and stamps" drops a fact.
|
|
16
|
+
- State a finding, a status or a prediction exactly as the evidence does, no weaker and no stronger: "will time out" stays "will time out", not "may slow down", and "CI is queued" never becomes "CI started". Add no assurance the evidence doesn't give.
|
|
17
|
+
- What goes is process noise: message ids, raw shas, PR numbers, sandbox paths, bot and tier names, and your own process. Name things in plain words ("the reviewer", "the fix", "the PR"), and put a sha, PR, message or file only behind a link whose label says what it is: [the fix](https://github.com/o/r/commit/abc).
|
|
18
|
+
- The commit a verdict, merge, rebase or CI result is about is a fact, not noise: keep it as a link, "green at [the reviewed head](https://github.com/o/r/commit/abc)".
|
|
19
|
+
- A fact that doesn't fit goes in the linked doc. It never gets dropped.
|
|
20
|
+
- **Show, don't tell**
|
|
5
21
|
- When what you explain has structure (parts that connect, events in order, numbers to compare, a before and after, a choice for the human), answer in one sentence, then draw it without being asked. A single fact stays a sentence.
|
|
6
22
|
- Why: prose arrives one item at a time, so the reader has to rebuild the structure in their head; a figure shows it at once.
|
|
7
23
|
- How: a `flow`, `sequence`, `chart` or `ask` form sent with `post_message`; load the `forms` skill for the shapes, tables and syntax.
|
|
@@ -21,10 +37,16 @@
|
|
|
21
37
|
- Why: a post that changes nothing still costs the reader the time to find that out.
|
|
22
38
|
- **Zero context**
|
|
23
39
|
- Define every name you coined the first time it appears in a thread.
|
|
24
|
-
- Link every message, PR, commit and file you mention
|
|
25
|
-
-
|
|
26
|
-
- What the reader will paste goes in a code fence with short lines; code formatting only for what they'll paste or search.
|
|
40
|
+
- Link every message, PR, commit and file you mention. Link labels name the target in plain words; never show a raw URL.
|
|
41
|
+
- What the reader will paste goes in a code fence with short lines; code formatting only for what they'll paste or search, and for code symbols.
|
|
27
42
|
- Why: each undefined name or unlinked reference costs the reader a round trip to resolve.
|
|
43
|
+
- **Patterns that read as machine-written** (`post_message` refuses the first two in a post the human reads)
|
|
44
|
+
- A bullet that opens with a bold label and a colon ("**Tests:** all pass"). Write the bullet as a sentence. A bold lead-in that ends in a period and names the item is fine.
|
|
45
|
+
- An em dash. End the sentence or use a comma.
|
|
46
|
+
- Colons as connectors mid-sentence, semicolons, and slashes for "or".
|
|
47
|
+
- Filler and hedging ("in order to", "it is important to note", "could potentially"), chatbot phrases ("Great question!", "Let me know if…"), and AI vocabulary (crucial, delve, leverage, robust, seamless, pivotal, underscore).
|
|
48
|
+
- Passive voice that hides who acted ("the fix was applied"): name the actor.
|
|
49
|
+
- Metaphors where a literal word exists (surface, wedge, harness, ratchet, north star).
|
|
28
50
|
- **Outside the workspace** (GitHub, Linear, Slack, email)
|
|
29
51
|
- You write under the human's name to people who never saw this workspace: lead with the finding and its evidence.
|
|
30
52
|
- Never show bot names, thread ids, verdict words, local paths or your own process. If unsure it belongs in public, ask the human first.
|
package/tools/post_message.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export declare const MAX_CHARS = 700;
|
|
|
3
3
|
export declare const postLength: (text: string) => number;
|
|
4
4
|
/** The error for a text the thread shouldn't carry, or null. */
|
|
5
5
|
export declare function textError(text: string): string | null;
|
|
6
|
+
/** The error for a post a human reads that uses a pattern writing.md rules out, or null. Code is never read. */
|
|
7
|
+
export declare function voiceError(text: string): string | null;
|
|
6
8
|
/** The text with each sandbox-path link target replaced by where the human's app opens it; a `#…` fragment stays on it. Each file is opened once. */
|
|
7
9
|
export declare function rewriteDocLinks(text: string, open: (path: string) => Promise<string>): Promise<string>;
|
|
8
10
|
declare const _default: import("eve/tools").ToolDefinition<{
|
package/tools/post_message.js
CHANGED
|
@@ -3,6 +3,7 @@ import { fromMarkdown } from "mdast-util-from-markdown";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { documentUrl, hostedHub, isSandboxPath, uploadBytes, uploadFile } from "../hub-upload.js";
|
|
5
5
|
import { bubbles } from "../bubbles.mjs";
|
|
6
|
+
import { mentionTokens } from "../mention-tokens.mjs";
|
|
6
7
|
import { registryFetch } from "../registry-fetch.mjs";
|
|
7
8
|
// Posts as you into a thread or DM, from whichever session you're in. With no
|
|
8
9
|
// target it posts into the thread you're in, as part of the running turn: once
|
|
@@ -17,6 +18,10 @@ import { registryFetch } from "../registry-fetch.mjs";
|
|
|
17
18
|
// rewritten to where the hub serves it (hub-upload.ts). Images go in `images`
|
|
18
19
|
// and ride the post.
|
|
19
20
|
//
|
|
21
|
+
// A post a human reads (it @-mentions no bot) is also refused for the
|
|
22
|
+
// mechanical patterns templates/writing.md rules out (voiceError). A handoff
|
|
23
|
+
// to a bot keeps its dense format: the app folds bot-to-bot chatter.
|
|
24
|
+
//
|
|
20
25
|
// `policy` is how a post reaches the one bot it @-mentions: queue (default)
|
|
21
26
|
// waits for that bot's turn to end, steer folds into its running turn, abort
|
|
22
27
|
// stops its turn and the text starts its next one.
|
|
@@ -57,6 +62,45 @@ export function textError(text) {
|
|
|
57
62
|
return `Too long: bubble ${over + 1} is over ${MAX_CHARS} characters; move the detail into a doc and link it`;
|
|
58
63
|
return null;
|
|
59
64
|
}
|
|
65
|
+
const plain = (node) => (node.type === "text" ? node.value ?? "" : (node.children ?? []).map(plain).join(""));
|
|
66
|
+
/** The error for a post a human reads that uses a pattern writing.md rules out, or null. Code is never read. */
|
|
67
|
+
export function voiceError(text) {
|
|
68
|
+
const found = new Set();
|
|
69
|
+
const visit = (node) => {
|
|
70
|
+
if (node.type === "text" && plain(node).includes("—"))
|
|
71
|
+
found.add("dash");
|
|
72
|
+
if (node.type === "listItem") {
|
|
73
|
+
const [first] = node.children ?? [];
|
|
74
|
+
const [lead, next] = first?.type === "paragraph" ? first.children ?? [] : [];
|
|
75
|
+
if (lead?.type === "strong" && (plain(lead).trim().endsWith(":") || (next?.type === "text" && plain(next).startsWith(":"))))
|
|
76
|
+
found.add("label");
|
|
77
|
+
}
|
|
78
|
+
node.children?.forEach(visit);
|
|
79
|
+
};
|
|
80
|
+
visit(fromMarkdown(text));
|
|
81
|
+
const why = [
|
|
82
|
+
found.has("label") && "a bullet that opens with a bold label and a colon: write the bullet as a sentence",
|
|
83
|
+
found.has("dash") && "an em dash: end the sentence or use a comma",
|
|
84
|
+
].filter(Boolean);
|
|
85
|
+
return why.length ? `A post a human reads can't have ${why.join("; or ")}. A post that @-mentions a bot is a handoff and keeps its format` : null;
|
|
86
|
+
}
|
|
87
|
+
/** Whether `text` @-mentions a bot (`@all` or a roster name), by the hub's own mention rule (mention-tokens.mjs). */
|
|
88
|
+
async function mentionsBot(text) {
|
|
89
|
+
const tokens = mentionTokens(text);
|
|
90
|
+
if (!tokens.length)
|
|
91
|
+
return false;
|
|
92
|
+
if (tokens.includes("all"))
|
|
93
|
+
return true;
|
|
94
|
+
const res = await registryFetch("/api/roster");
|
|
95
|
+
const body = await res.text();
|
|
96
|
+
if (!res.ok)
|
|
97
|
+
throw new Error(`GET /api/roster: HTTP ${res.status}: ${body.slice(0, 300)}`);
|
|
98
|
+
const roster = JSON.parse(body);
|
|
99
|
+
if (!Array.isArray(roster))
|
|
100
|
+
throw new Error(`GET /api/roster: not a list of bots: ${body.slice(0, 300)}`);
|
|
101
|
+
const bots = new Set(roster.map((b) => b.name.toLowerCase()));
|
|
102
|
+
return tokens.some((t) => bots.has(t));
|
|
103
|
+
}
|
|
60
104
|
const PATH_AND_FRAGMENT = /^([^?#]*)(?:\?[^#]*)?(#.*)?$/;
|
|
61
105
|
/** The text with each sandbox-path link target replaced by where the human's app opens it; a `#…` fragment stays on it. Each file is opened once. */
|
|
62
106
|
export async function rewriteDocLinks(text, open) {
|
|
@@ -95,7 +139,7 @@ async function screenshot(sessionId) {
|
|
|
95
139
|
return browser.screenshot();
|
|
96
140
|
}
|
|
97
141
|
export default defineTool({
|
|
98
|
-
description: "Post a message as you. Leave out threadId and agent to post into the thread you're in, while you work: posting doesn't end your turn, and once your turn has posted into this thread, its end-of-turn text isn't posted, so put your result in a post. Or post into another workspace thread or a bot's DM. Each bubble is at most 700 characters (link targets don't count); a `---` line with blank lines around it starts a new bubble. Lead with the point in 2 sentences, then at most 3 bullets or one small table. Depth goes in a document: write it to a file and link it in the text by its sandbox path, `[the plan](/workspace/plan.md)`; the file is uploaded and the link opens it beside the thread. Images go in `images` (sandbox paths; `screenshot` for your browser's page on a local hub) and show inline; never write `![]()`. @-mentions wake bots the same way your replies do (at most one, never yourself); `@all` wakes every participant; each bot's gate decides whether it replies. Posting into a bot's DM wakes that bot. Archived threads are refused. `policy` says how a post reaches the one bot it @-mentions: queue (default) runs as its next turn, or starts one now if it is idle; steer lands in its RUNNING turn at the next step boundary and the bot folds it in without a restart, to correct course mid-turn; abort stops its turn in that thread (running commands are killed, the process keeps running) and the text starts its fresh turn, \"stop, do this instead\". Steer and abort need exactly one @-mention, not `@all`, and cut into work already under way: use them only to change what a running turn is doing. A status post sets the thread's phase: planning, working, reviewing or done; allowed moves are start → planning or working, planning → working, working → reviewing, reviewing → working (fix first), planning (rethink) or done. Any other move is refused with the allowed next phases. The text is a phase marker, not a report: at most 8 plain words on what is happening now (\"Building the input-count fix\"), with no bot or role names, no restating the phase, no parentheticals. Test results, branch names and caveats go in a separate message. The PR URL and sha go in metadata, never in the text.",
|
|
142
|
+
description: "Post a message as you. Leave out threadId and agent to post into the thread you're in, while you work: posting doesn't end your turn, and once your turn has posted into this thread, its end-of-turn text isn't posted, so put your result in a post. Or post into another workspace thread or a bot's DM. Each bubble is at most 700 characters (link targets don't count); a `---` line with blank lines around it starts a new bubble. Lead with the point in 2 sentences, then at most 3 bullets or one small table. A post that @-mentions no bot is one the human reads, and it is refused for a bullet that opens with a bold label and a colon, or an em dash. Depth goes in a document: write it to a file and link it in the text by its sandbox path, `[the plan](/workspace/plan.md)`; the file is uploaded and the link opens it beside the thread. Images go in `images` (sandbox paths; `screenshot` for your browser's page on a local hub) and show inline; never write `![]()`. @-mentions wake bots the same way your replies do (at most one, never yourself); `@all` wakes every participant; each bot's gate decides whether it replies. Posting into a bot's DM wakes that bot. Archived threads are refused. `policy` says how a post reaches the one bot it @-mentions: queue (default) runs as its next turn, or starts one now if it is idle; steer lands in its RUNNING turn at the next step boundary and the bot folds it in without a restart, to correct course mid-turn; abort stops its turn in that thread (running commands are killed, the process keeps running) and the text starts its fresh turn, \"stop, do this instead\". Steer and abort need exactly one @-mention, not `@all`, and cut into work already under way: use them only to change what a running turn is doing. A status post sets the thread's phase: planning, working, reviewing or done; allowed moves are start → planning or working, planning → working, working → reviewing, reviewing → working (fix first), planning (rethink) or done. Any other move is refused with the allowed next phases. The text is a phase marker, not a report: at most 8 plain words on what is happening now (\"Building the input-count fix\"), with no bot or role names, no restating the phase, no parentheticals. Test results, branch names and caveats go in a separate message. The PR URL and sha go in metadata, never in the text.",
|
|
99
143
|
inputSchema: z.object({
|
|
100
144
|
threadId: z.string().optional().describe("Another thread's id, e.g. thr_0002, or a DM id, dm-<bot>. Omit it to post into the thread you're in."),
|
|
101
145
|
agent: z.string().optional().describe("Post into this bot's DM instead of a thread."),
|
|
@@ -121,6 +165,9 @@ export default defineTool({
|
|
|
121
165
|
const bad = textError(text);
|
|
122
166
|
if (bad)
|
|
123
167
|
throw new Error(bad);
|
|
168
|
+
const voice = voiceError(text);
|
|
169
|
+
if (voice && !(await mentionsBot(text)))
|
|
170
|
+
throw new Error(voice);
|
|
124
171
|
const target = threadId ?? (agent ? `dm-${agent.replace(/^@/, "")}` : "here");
|
|
125
172
|
let sandbox;
|
|
126
173
|
const files = async () => (sandbox ??= await ctx.getSandbox());
|