eve-hub 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # eve-hub
2
2
 
3
+ ## 0.8.2
4
+
5
+ ### Patch Changes
6
+
7
+ - The per-bot mention gate is gone: the hub decides who a bot's message wakes (wake router), so the `hooks/mention-gate` hook is removed from the kit.
8
+
9
+ ## 0.8.1
10
+
11
+ ### Patch Changes
12
+
13
+ - A turn whose last step is only comms actions (a plain post into its thread, react) posts no end-of-turn text. Tool results declare `comms: true` (post_message's `own` is now `comms`), so a bot that reacts and ends no longer posts a "nothing to do" message.
14
+
3
15
  ## 0.8.0
4
16
 
5
17
  ### Minor Changes
package/kit-modules.json CHANGED
@@ -2,7 +2,6 @@
2
2
  "channels/eve",
3
3
  "channels/eve-roster-user",
4
4
  "connections/vercel-kit",
5
- "hooks/mention-gate",
6
5
  "hooks/sandbox",
7
6
  "instructions/engineering",
8
7
  "instructions/main",
@@ -2,5 +2,4 @@ export type GateConfig = { enabled: boolean; model: string; api: 'chat' | 'decis
2
2
  export function gateConfig(env?: Record<string, string | undefined>, file?: unknown): GateConfig;
3
3
  export function opensWithHandle(text: string, agent: string): boolean;
4
4
  export function cleanReason(reason: unknown): string;
5
- export function screenInbound(input: { text: string; sessionId: string; agent?: string; registryUrl: string; credential: (opts?: { refresh?: boolean }) => Promise<{ token: string; teamId?: string | null } | null>; instructions?: string; fetchImpl?: typeof fetch; log?: ((line: string) => void) | null }): Promise<{ cancel: boolean; result?: unknown }>;
6
5
  export function hubCredentialLoader(fetchHub: (path: string, init: { headers?: Record<string, string> }) => Promise<Response>): (opts?: { refresh?: boolean }) => Promise<{ ok: boolean; token?: string; error?: string }>;
package/mention-gate.mjs CHANGED
@@ -1,19 +1,7 @@
1
- // Mention gate: when a bot's turn is woken by an @-mention or a DM, the bot
2
- // itself (its message.received hook, hooks/mention-gate.ts) asks a cheap
3
- // model, through AI Gateway, whether a reply is needed, before its main model
4
- // runs. The model reads the bot's own instructions and the delivery the bot
5
- // just received. A handle inside code (codeRanges) is dropped by
6
- // parseMentions before it joins or wakes anyone; the gate catches the other
7
- // incidental ones: a quote or a recap, or an ack that asks nothing.
8
- //
9
- // The hub decides which wakes are gated (gatedWake, registry/server.mjs) and
10
- // owns the outcome: the hook asks it first (POST /api/mention-gate/check),
11
- // then reports the decision (POST /api/mention-gate/report); on a skip the
12
- // hub posts the system line and records the usage, and the hook cancels the
13
- // turn. Biased to answering: the model only classifies the message (how the
14
- // handle is used, what it asks of the bot); skipFor decides, and any error,
15
- // timeout or unreadable verdict answers. Only bots' messages are gated: the
16
- // human's always wake.
1
+ // The decision-model helpers the registry's mention gate (registry/server.mjs, frozen until its removal) and the hosted hub's wake
2
+ // router and closing check share: the gateway config (gateConfig), the Mac bot credential loader and the response parsing. The bot-side
3
+ // hook is gone; the hosted hub decides who a message wakes (hosted-hub/src/store/wake-router.ts). A handle inside code (codeRanges)
4
+ // is dropped by parseMentions before it joins or wakes anyone.
17
5
  //
18
6
  // Config (the hub's, sent to the hook with each check): `mentionGate` in
19
7
  // hub.workspace.json ({ enabled, model, reasoningEffort, timeoutMs }),
@@ -21,7 +9,6 @@
21
9
  // HUB_MENTION_GATE_REASONING, HUB_MENTION_GATE_TIMEOUT_MS and
22
10
  // HUB_MENTION_GATE_URL (tests).
23
11
  import { codeRanges } from "./mention-tokens.mjs";
24
- import { hubHeaders } from "./registry-fetch.mjs";
25
12
  import { createHash } from "node:crypto";
26
13
 
27
14
  export { codeRanges };
@@ -395,45 +382,6 @@ export function credentialCache(load, { ttlMs = 10 * 60e3, failMs = 60e3, now =
395
382
  return get;
396
383
  }
397
384
 
398
- // --- The bot's side (hooks/mention-gate.ts).
399
-
400
- const HUB_CALL_MS = 2000;
401
-
402
- async function hubCall(fetchImpl, url, body) {
403
- const res = await fetchImpl(url, { method: "POST", headers: await hubHeaders({ "content-type": "application/json" }), body: JSON.stringify(body), signal: AbortSignal.timeout(HUB_CALL_MS) });
404
- if (!res.ok) throw new Error(`hub ${res.status}`);
405
- return res.json();
406
- }
407
-
408
- // One inbound delivery, screened. Never throws; { cancel: true } only when the
409
- // hub accepted a skip (it posted the system line and expects the turn to
410
- // end cancelled). Anything that fails on the way answers.
411
- // text: the delivery (message.received's data.message)
412
- // credential: ({ refresh }) → { token, teamId? } | null (credentialCache)
413
- // `agent` is optional: left out, the hub names the bot from its session and sends its instructions.
414
- export async function screenInbound({ text, sessionId, agent, registryUrl, instructions = "", credential, fetchImpl = fetch, log = console.warn }) {
415
- const messageId = triggerId(text);
416
- if (!registryUrl || !messageId) return { cancel: false };
417
- let check;
418
- try {
419
- check = await hubCall(fetchImpl, `${registryUrl}/api/mention-gate/check`, { ...(agent ? { agent } : {}), messageId, sessionId });
420
- } catch (error) {
421
- log?.(`[mention-gate] check for ${messageId} failed, answering: ${error?.message ?? error}`);
422
- return { cancel: false };
423
- }
424
- if (!check?.gate || !check.config) return { cancel: false };
425
- agent ??= check.agent;
426
- instructions ||= check.instructions ?? "";
427
- const r = await decide({ config: check.config, credential, prompt: promptFor(check.config, { agent, human: check.humanName ?? "the human", instructions, envelope: text, broadcast: Boolean(check.broadcast) }), addressed: Boolean(check.addressed), fetchImpl });
428
- try {
429
- const ack = await hubCall(fetchImpl, `${registryUrl}/api/mention-gate/report`, { ...(agent ? { agent } : {}), messageId, sessionId, skip: r.skip, reason: r.reason, verdict: r.verdict ?? null, usage: r.usage ?? null, ms: r.ms, error: r.error ?? null });
430
- return { cancel: Boolean(r.skip && ack?.skip), result: r };
431
- } catch (error) {
432
- log?.(`[mention-gate] report for ${messageId} failed, answering: ${error?.message ?? error}`);
433
- return { cancel: false, result: r };
434
- }
435
- }
436
-
437
385
  // A Mac bot's AI Gateway credential: the hub mints an OIDC token for the workspace's bots project, asked for with the bot's HUB_TOKEN.
438
386
  // `fetchHub(path, init)` is registryFetch. On a refresh it names the token AI Gateway refused, as eve's served credential does.
439
387
  export function hubCredentialLoader(fetchHub) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eve-hub",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
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/stubs.mjs CHANGED
@@ -4,7 +4,6 @@
4
4
  export const KIT_FILES = {
5
5
  "channels/eve.ts": ["Queued eve channel that resolves the registry's human as a user.", "channels/eve-roster-user.ts"],
6
6
  "hooks/sandbox.ts": ["Migrates legacy Docker sessions to the host sandbox."],
7
- "hooks/mention-gate.ts": ["Mention gate: a cheap model decides whether an inbound @-mention or DM needs a turn."],
8
7
  "tools/bash.ts": ["eve's bash with a default time limit (kills the process group) and the workspace's code location."],
9
8
  "tools/react.ts": ["React to a message with an emoji."],
10
9
  "tools/show.ts": ["Move the human's screen to a thread, DM, or message."],
@@ -52,7 +51,6 @@ export const MAIN_KIT_FILES = {
52
51
  // main's dash schedule stays: off the hub's machine it has no PR data (its lookup runs `gh`).
53
52
  export const DEPLOYED_KIT_FILES = new Set([
54
53
  "channels/eve.ts",
55
- "hooks/mention-gate.ts",
56
54
  "schedules/dash.ts",
57
55
  "skills/onboarding-profile.ts",
58
56
  "tools/onboarding_scripts.ts",
@@ -4,7 +4,7 @@ You are one named bot in a small workspace of long-lived bots. Work happens in t
4
4
 
5
5
  - Each message you receive starts with the thread name, the live workspace state (every bot, you included, with its role, model, status, the threads it's busy in and its last message), and everything posted since your last turn. The line marked `→` is the one addressed to you.
6
6
  - Talk in the thread with `post_message` (no `threadId`): each call is one message under your name, and posting doesn't end your turn. Post when you start work that takes a while, when you find something that matters, and when you're done (principle 1). Text you write between tool calls is not posted.
7
- - If your last step was only plain `post_message` calls into this thread, and each succeeded, end the turn without text: anything you write after it goes to your activity log, not the thread. A status post, a post to another thread or DM, a failed post, or a step that also called another tool doesn't count: then your final text is posted as your last message, so write the answer there. That way an answer you forget to post isn't lost.
7
+ - If your last step was only comms actions (plain `post_message` calls into this thread, and `react`), and each succeeded, end the turn without text: anything you write after it goes to your activity log, not the thread. A status post, a post to another thread or DM, a failed call, or a step that also called any other tool doesn't count: then your final text is posted as your last message, so write the answer there. Never write a closing line that says you reacted or have nothing to do. That way an answer you forget to post isn't lost.
8
8
  - Every @-mention wakes that bot and costs a turn. Mention someone only when you need them to act **now**. At most one @-mention per message, in the message that hands off, once your work is ready: the one bot or human who should act next. A progress post ("looking now") mentions nobody. Refer to anyone else by plain name (no @). Never @-mention yourself.
9
9
  - `@all` wakes every participant in the thread; each bot's gate decides whether it replies, so news that asks nothing of a bot costs it no turn. A bot that's mid-turn gets it when that turn ends. It doesn't count toward the one-mention rule, and a named @-mention beside it still addresses that bot by name. Use it when everyone in the thread should hear something; when one bot should act, @-mention that bot.
10
10
  - Rows from `(hub)` in your log are hub events: who joined or left the thread. They wake nobody. A message marked `[status: …]` is a bot's status post: the thread's phase, its PR and sha, with the text as the summary.
@@ -22,7 +22,7 @@ declare const _default: import("eve/tools").ToolDefinition<{
22
22
  threadId: string;
23
23
  title: string;
24
24
  deliveries: Record<string, string>;
25
- own?: boolean;
25
+ comms?: boolean;
26
26
  }> & {
27
27
  execute(input: {
28
28
  threadId?: string | undefined;
@@ -48,7 +48,7 @@ declare const _default: import("eve/tools").ToolDefinition<{
48
48
  threadId: string;
49
49
  title: string;
50
50
  deliveries: Record<string, string>;
51
- own?: boolean;
51
+ comms?: boolean;
52
52
  }>;
53
53
  };
54
54
  export default _default;
@@ -4,7 +4,7 @@ import { registryFetch } from "../registry-fetch.mjs";
4
4
  // Posts as you into a thread or DM, from whichever session you're in. With no
5
5
  // target it posts into the thread you're in, as part of the running turn: it
6
6
  // carries the turn's id and the images collected so far,
7
- // and if it's your turn's last act, your end-of-turn text isn't posted. Into
7
+ // and if it's your turn's last act (alone or with reactions), your end-of-turn text isn't posted. Into
8
8
  // another thread, mentions wake bots exactly as your replies do; posting into
9
9
  // a bot's DM wakes that bot; archived threads refuse it. Your session in the
10
10
  // target thread, if any, isn't woken; it sees the message as yours the next
@@ -18,7 +18,7 @@ import { registryFetch } from "../registry-fetch.mjs";
18
18
  // move), the text is its summary, `metadata` its PR and sha. The hub keeps the
19
19
  // latest one per bot and thread for the workspace state and the Dash.
20
20
  export default defineTool({
21
- 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 when a post is your turn's last act your end-of-turn text isn't posted. Or post into another workspace thread or a bot's DM. @-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.",
21
+ 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 when your turn's last step is only comms (a plain post into this thread, a reaction), your end-of-turn text isn't posted. Or post into another workspace thread or a bot's DM. @-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.",
22
22
  inputSchema: z.object({
23
23
  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."),
24
24
  agent: z.string().optional().describe("Post into this bot's DM instead of a thread."),
package/tools/react.d.ts CHANGED
@@ -3,12 +3,14 @@ declare const _default: import("eve/tools").ToolDefinition<{
3
3
  emoji: string;
4
4
  }, {
5
5
  ok: boolean;
6
+ comms: boolean;
6
7
  }> & {
7
8
  execute(input: {
8
9
  messageId: string;
9
10
  emoji: string;
10
11
  }, ctx: import("eve/tools").ToolContext): Promise<{
11
12
  ok: boolean;
13
+ comms: boolean;
12
14
  }>;
13
15
  };
14
16
  export default _default;
package/tools/react.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { defineTool } from "eve/tools";
2
2
  import { z } from "zod";
3
3
  import { registryFetch } from "../registry-fetch.mjs";
4
- // Reactions are the bot's own choice, not delivery bookkeeping.
4
+ // Reactions are the bot's own choice, not delivery bookkeeping. `comms: true` declares it talks in the thread: a turn whose last step is only comms posts no end-of-turn text.
5
5
  export default defineTool({
6
- description: "React to a message in this thread with an emoji (toggles). Use the [msg_…] id shown before each message. Good for light acknowledgement (👍 got it, 👀 looking, ✅ done, 🎉 nice) when a full reply isn't needed.",
6
+ description: "React to a message in this thread with an emoji (toggles). Use the [msg_…] id shown before each message. Good for light acknowledgement (👍 got it, 👀 looking, ✅ done, 🎉 nice) when a full reply isn't needed. A turn that ends on only reactions and posts posts no closing text, so never write one.",
7
7
  inputSchema: z.object({
8
8
  messageId: z.string().describe("The id in brackets before the message, e.g. msg_0012."),
9
9
  emoji: z.string().describe("A single emoji."),
@@ -16,6 +16,6 @@ export default defineTool({
16
16
  });
17
17
  if (!res.ok)
18
18
  throw new Error(`react failed: ${res.status} ${await res.text()}`);
19
- return { ok: true };
19
+ return { ok: true, comms: true };
20
20
  },
21
21
  });