eve-hub 0.16.37 → 0.16.39

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/bot-model.mjs CHANGED
@@ -57,9 +57,14 @@ export const GATEWAY_PROVIDERS = Object.freeze({
57
57
 
58
58
  // Provider options that fit `model`, and only those. Reasoning text is opt-in
59
59
  // on current Anthropic and OpenAI models; ask for summaries so the UI can
60
- // interleave them with tool calls.
60
+ // interleave them with tool calls. Gateway's automatic caching marks only the
61
+ // latest messages, so a resumed session never finds the system prompt another
62
+ // session already cached; anchoring message 0 lets it read that prefix.
61
63
  export function modelOptionsFor(model) {
62
- if (model.startsWith("anthropic/")) return { providerOptions: { anthropic: { thinking: { type: "adaptive", display: "summarized" } } } };
64
+ if (model.startsWith("anthropic/")) return { providerOptions: {
65
+ anthropic: { thinking: { type: "adaptive", display: "summarized" } },
66
+ gateway: { cacheAnchorMessageIndex: 0 },
67
+ } };
63
68
  if (model.startsWith("openai/")) return { providerOptions: { openai: { reasoningSummary: "auto" } } };
64
69
  const order = GATEWAY_PROVIDERS[model];
65
70
  return order ? { providerOptions: { gateway: { order: [...order] } } } : undefined;
package/kit-modules.json CHANGED
@@ -35,7 +35,6 @@
35
35
  "tools/remove_agent",
36
36
  "tools/restart_agent",
37
37
  "tools/restart_hub",
38
- "tools/set_summary",
39
38
  "tools/show",
40
39
  "tools/tag_threads",
41
40
  "tools/unarchive_thread",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eve-hub",
3
- "version": "0.16.37",
3
+ "version": "0.16.39",
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/prepare.mjs CHANGED
@@ -88,15 +88,15 @@ import { isHubRequest } from "eve-hub/hub-oidc";
88
88
  import { parseWorkspace } from "eve-hub/workspace";
89
89
  import workspace from "../../../../hub.manifest.json" with { type: "json" };
90
90
 
91
- // The roster, humans and subagent cap as deployed, Mac bots included, and the eve-hub and eve versions that built it, for the hub to sync from:
91
+ // The roster, humans, subagent cap, hop limit and shims as deployed, Mac bots included, and the eve-hub and eve versions that built it, for the hub to sync from:
92
92
  // hub.manifest.json is bundled at build, so a push whose deploy fails never
93
93
  // changes it. \`sha\` is the commit the deployment was built from, which the hub
94
94
  // syncs by. An uploaded deployment (onboarding's first, built while the repo is
95
95
  // made) has no commit, so it answers its own deployment id, which the hub reads
96
96
  // from the project's production deployment.
97
97
  const sha = process.env.VERCEL_GIT_COMMIT_SHA || process.env.VERCEL_DEPLOYMENT_ID;
98
- const { agents, humans, subagentCap, hopLimit } = parseWorkspace(workspace);
99
- const manifest = { sha, kit: ${JSON.stringify(KIT_VERSION)}, eve: ${JSON.stringify(EVE_VERSION)}, agents, humans, subagentCap, ...(hopLimit === undefined ? {} : { hopLimit }) };
98
+ const { agents, humans, subagentCap, hopLimit, shims } = parseWorkspace(workspace);
99
+ const manifest = { sha, kit: ${JSON.stringify(KIT_VERSION)}, eve: ${JSON.stringify(EVE_VERSION)}, agents, humans, subagentCap, ...(hopLimit === undefined ? {} : { hopLimit }), ...(shims === undefined ? {} : { shims }) };
100
100
 
101
101
  export default defineChannel({
102
102
  routes: [
package/stubs.mjs CHANGED
@@ -13,7 +13,7 @@ export const KIT_FILES = {
13
13
  "tools/browser.ts": ["Its own headless browser, watched live in the Computer view."],
14
14
  "tools/fetch_upload.ts": ["Copies a file another bot shared (hub://upload/<id>) into the sandbox."],
15
15
  "tools/view_image.ts": ["Shows the bot an image (a sandbox file or a browser screenshot) so it can see it."],
16
- "tools/update_thread.ts": ["Update a thread this bot is in: title, track, domain and summary."],
16
+ "tools/update_thread.ts": ["Update a thread this bot is in: title, track and domain."],
17
17
  "tools/list_threads.ts": ["Every workspace thread, from any session."],
18
18
  "tools/read_thread.ts": ["Read any workspace thread."],
19
19
  "tools/list_agents.ts": ["The team right now: kind, place, tools, models, who's busy."],
@@ -30,7 +30,6 @@ export const MAIN_KIT_FILES = {
30
30
  "skills/onboarding-profile.ts": ["The onboarding-profile skill (packages/agent-kit/onboarding/SKILL.md): after sign-in, reads the user's Slack and GitHub and posts a profile with suggested schedules."],
31
31
  "tools/onboarding_scripts.ts": ["Writes the onboarding-profile skill's sandbox scripts into the sandbox."],
32
32
  "tools/create_thread.ts": ["Open a workspace thread for one piece of work."],
33
- "tools/set_summary.ts": ["Write the summary of a path (project/track) or a thread."],
34
33
  "tools/move_path.ts": ["Rename or merge a path (project/track): every thread under it moves."],
35
34
  "tools/archive_thread.ts": ["Archive a thread whose work is done."],
36
35
  "tools/unarchive_thread.ts": ["Bring an archived thread back."],
@@ -61,7 +60,7 @@ export const DEPLOYED_KIT_FILES = new Set([
61
60
  "memory/team.ts",
62
61
  "skills/forms.ts",
63
62
  "skills/fanout.ts",
64
- ...["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`),
63
+ ...["create_thread", "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`),
65
64
  ]);
66
65
 
67
66
  // ---- stub text: what each kit stub file says ----
@@ -1,3 +1,3 @@
1
1
  - **Team.** `create_agent` adds a bot to the workspace; if nobody on the team covers the work well, create a specialist (name, model, role, instructions), then route to it. Give planners and reviewers `preset: "read-only"` so they can't edit files. `remove_agent` removes a bot from the team and shuts it down (name); use it when the human asks. It can't remove you (the coordinator) or humans.
2
2
  - **Memory.** A new memory entry ships in the next workspace sync PR; each bot recalls it on its next turn, as a `Standing decision <id>:` or `Your standing decision <id>:` message.
3
- - **Summaries.** A track summary you write with `set_summary` stands until you write another. `list_threads` returns thread and track summaries.
3
+ - **Summaries.** The hub derives each thread's state (who it waits on, since when, the next step) after every post, and each track's summary from its threads. `list_threads` returns both; nobody writes them.
@@ -21,6 +21,6 @@ You are the **coordinator**, the human's chief of staff. The human talks to you
21
21
  - **A merge can unblock other threads:** on a merge report, `list_threads` for threads that wait on it (a PR stacked on it, a build or release of it, a follow-up gated on it), and in the same turn post in each to its owning bot with its next step (rebase, build, start).
22
22
  - **A post addressed to you (a report, verdict or answer):** never end the turn on a reaction alone. Reply with the next step: who acts, on what, by when. If the step is a wait (CI, a run, the human), name it and arm a wake-up. React alone only when the post changes nothing and the next step is already named, armed and live.
23
23
 
24
- **Keep the workspace legible.** Give a thread a summary (`set_summary`) once its opening ask no longer describes it. Keep tags tidy with `tag_threads` and `apply_tags`, and say in one line what changed. On the Dash, the hub sets ready, and a deferral the human made stays. Tags and paths the human set stay too. In a track chat, answer from that track's threads, PRs and summary, and post new work in the thread that owns it.
24
+ **Keep the workspace legible.** Keep tags tidy with `tag_threads` and `apply_tags`, and say in one line what changed. On the Dash, the hub sets ready, and a deferral the human made stays. Tags and paths the human set stay too. In a track chat, answer from that track's threads, their states and PRs, and post new work in the thread that owns it.
25
25
 
26
26
  **Memory.** A preference that should outlast the task becomes `memory/<id>.md` (every bot) or `agents/<bot>/memory/<id>.md` (one bot). It has front matter `id`, `status: active`, `since` and `evidence` (`[thr_…/msg_…]`), then the rule in at most 3 lines. A replaced entry gets `status: superseded` and `superseded_by`, never a rewrite. Keep `memory/human.md` (who the human is and how they work) current the same way.
@@ -7,6 +7,7 @@ declare const _default: import("eve/tools").ToolDefinition<{
7
7
  project?: string | undefined;
8
8
  path?: string | undefined;
9
9
  issueType?: string | undefined;
10
+ waitingOn?: string | undefined;
10
11
  limit?: number | undefined;
11
12
  }, any> & {
12
13
  execute(input: {
@@ -18,6 +19,7 @@ declare const _default: import("eve/tools").ToolDefinition<{
18
19
  project?: string | undefined;
19
20
  path?: string | undefined;
20
21
  issueType?: string | undefined;
22
+ waitingOn?: string | undefined;
21
23
  limit?: number | undefined;
22
24
  }, ctx: import("eve/tools").ToolContext): Promise<any>;
23
25
  };
@@ -5,7 +5,7 @@ import { defineTool } from "eve/tools";
5
5
  import { z } from "zod";
6
6
  import { registryFetch } from "../registry-fetch.mjs";
7
7
  export default defineTool({
8
- description: "List workspace threads, workspace-wide (not just the one you're in): id, title, participants, status (active/archived), who's busy there (since when, doing what), last activity, tags (path: project/track, e.g. 'web/checkout'; project; issue type), and the thread's stored summary when main wrote one. Newest activity first. `paths` holds the listed threads' paths with each one's current summary (what's up in that feature). Use it to find a thread by title or summary before asking someone, and read_thread to read one.",
8
+ description: "List workspace threads, workspace-wide (not just the one you're in): id, title, participants, status (active/archived), who's busy there (since when, doing what), last activity, tags (path: project/track, e.g. 'web/checkout'; project; issue type), and where the thread stands: `state` (who it waits on: `human`, a bot, `ci`, or null for nothing pending; `since`; the `next` line; the message it rests on), which the hub derives after every post, and `summary`, the same as one line. Newest activity first. `paths` holds the listed threads' paths, each summarised from its threads' states. Pass `waitingOn: \"human\"` for the threads waiting on the human, the longest waiting first. Use it to find a thread by title or summary before asking someone, and read_thread to read one.",
9
9
  inputSchema: z.object({
10
10
  query: z.string().optional().describe("Match threads whose title or summary contains this text (or an exact id). Title matches come first."),
11
11
  archived: z.enum(["exclude", "include", "only"]).optional().describe("Archived threads: exclude (default), include, or only."),
@@ -15,6 +15,7 @@ export default defineTool({
15
15
  project: z.string().optional().describe("Only threads with this project tag ('none' for untagged)."),
16
16
  path: z.string().optional().describe("Only threads on this path or under it, e.g. 'web' or 'web/checkout'."),
17
17
  issueType: z.string().optional().describe("Only threads with this issue type tag ('none' for untagged)."),
18
+ waitingOn: z.string().optional().describe("Only threads waiting on this party: 'human', a bot name, 'ci', or 'nobody'. Sorted longest waiting first."),
18
19
  limit: z.number().int().min(1).max(500).optional().describe("Max threads (default 50)."),
19
20
  }),
20
21
  async execute(input) {
@@ -3,7 +3,6 @@ declare const _default: import("eve/tools").ToolDefinition<{
3
3
  title?: string | undefined;
4
4
  path?: string | null | undefined;
5
5
  issueType?: string | undefined;
6
- summary?: string | undefined;
7
6
  pathLabel?: string | undefined;
8
7
  projectLabel?: string | undefined;
9
8
  issueTypeLabel?: string | undefined;
@@ -18,7 +17,6 @@ declare const _default: import("eve/tools").ToolDefinition<{
18
17
  title?: string | undefined;
19
18
  path?: string | null | undefined;
20
19
  issueType?: string | undefined;
21
- summary?: string | undefined;
22
20
  pathLabel?: string | undefined;
23
21
  projectLabel?: string | undefined;
24
22
  issueTypeLabel?: string | undefined;
@@ -1,22 +1,22 @@
1
1
  // The `update_thread` tool, shared by every bot: say what a thread is, in one
2
- // call. Title, path (project/track), issue type and the thread's summary. The
3
- // registry keeps the rename history and posts an inline "<bot> renamed this
4
- // thread: old → new" marker; tags go on thread.tags (lib/tags.mjs) and the
5
- // summary is the one `set_summary` writes (lib/paths.mjs setSummary). `artifacts`
6
- // adds Artifacts pane entries with no post: the hub writes one silent event row each.
2
+ // call. Title, path (project/track) and issue type. The registry keeps the
3
+ // rename history and posts an inline "<bot> renamed this thread: old → new"
4
+ // marker; tags go on thread.tags (lib/tags.mjs). The thread's summary is the
5
+ // hub's, derived from its posts (hosted-hub/src/shims/index.ts).
6
+ // `artifacts` adds Artifacts pane entries with no post: the hub writes one
7
+ // silent event row each.
7
8
  import { defineTool } from "eve/tools";
8
9
  import { z } from "zod";
9
10
  import { registryFetch } from "../registry-fetch.mjs";
10
11
  import { documentUrl } from "../hub-upload.js";
11
12
  import { artifactInput, artifactUrl } from "./post_message.js";
12
13
  export default defineTool({
13
- description: "Update a workspace thread you're in: its title, track, issue type and summary, in one call. Retitle when the work's scope has changed and the old title no longer fits: everyone sees a short 'renamed this thread: old → new' line; the history is kept. Keep titles short (at most 80 characters). File it on a path (project/track, e.g. 'web/checkout': the first segment is the project, the second the track) and tag an issue type (the kind of work, e.g. 'feature', 'bugfix', 'infra'). Reuse a project, track and issue type in use whenever one fits. File under the project alone until at least 3 threads share a track. For a new tag, also give its display name (projectLabel, pathLabel or issueTypeLabel, e.g. 'Payments API'). The summary is a short note on what the thread is about and where it stands; it replaces the previous one, and empty text clears it. The reply's `inUse` lists the tags in use with their thread counts and flags a tag no other thread uses; when one in use fits better than yours, call update_thread again with it. Tags a human set by hand stay. DMs and archived threads can't be updated.",
14
+ description: "Update a workspace thread you're in: its title, track and issue type, in one call. Retitle when the work's scope has changed and the old title no longer fits: everyone sees a short 'renamed this thread: old → new' line; the history is kept. Keep titles short (at most 80 characters). File it on a path (project/track, e.g. 'web/checkout': the first segment is the project, the second the track) and tag an issue type (the kind of work, e.g. 'feature', 'bugfix', 'infra'). Reuse a project, track and issue type in use whenever one fits. File under the project alone until at least 3 threads share a track. For a new tag, also give its display name (projectLabel, pathLabel or issueTypeLabel, e.g. 'Payments API'). The thread's summary isn't yours to write: the hub derives where it stands from its posts after each one. The reply's `inUse` lists the tags in use with their thread counts and flags a tag no other thread uses; when one in use fits better than yours, call update_thread again with it. Tags a human set by hand stay. DMs and archived threads can't be updated.",
14
15
  inputSchema: z.object({
15
16
  threadId: z.string().describe("Thread id, e.g. thr_0002 (shown in the [thread: …] header)."),
16
17
  title: z.string().optional().describe("New short title, e.g. 'Checkout: retry failed payments'. Omit to leave it."),
17
18
  path: z.string().nullable().optional().describe("Path: project, then at most one track, e.g. 'web/checkout' or 'api/billing'. Reuse a path in use (the reply's inUse and list_threads show them); start a track only once at least 3 threads share it. null: back to the project alone. A path a human set by hand stays."),
18
19
  issueType: z.string().optional().describe("Issue type tag: the kind of work, e.g. 'feature', 'bugfix', 'infra', 'docs', 'review', 'research'. Lowercase slug."),
19
- summary: z.string().optional().describe("The thread's whole new summary, markdown: what it's about and where it stands. Empty clears it. Omit to leave it."),
20
20
  pathLabel: z.string().optional().describe("Display name for a new path's last segment, e.g. 'Cart'. Only names a path that has none yet."),
21
21
  projectLabel: z.string().optional().describe("Display name for a new project (a path's first segment), shown as its sidebar group, e.g. 'Payments API' for payments-api. Only names a project that has none yet."),
22
22
  issueTypeLabel: z.string().optional().describe("Display name for a new issue type tag, e.g. 'Bug fixes' for bugfix. Only names a tag that has none yet."),
package/workspace.mjs CHANGED
@@ -12,6 +12,7 @@
12
12
  // "subagentCap"?: { "model": "…", "reasoning": "low", "models": ["…"] },
13
13
  // "limits"?: { "maxTokenCostUsdPerSession": 75 },
14
14
  // "hopLimit"?: 20,
15
+ // "shims"?: { "thread-tracker": true, "thread-post-model": true, "thread-sitrep": false },
15
16
  // "code"?: ["~/wrk/hub"],
16
17
  // "cachePaths"?: ["node_modules", "packages/kit/node_modules"]
17
18
  // }
@@ -25,6 +26,9 @@
25
26
  // bot delivery is blocked (a positive integer). Absent or null: no limit. The
26
27
  // hub edits it at runtime (Settings, PUT /api/hop-limit); no limit removes it.
27
28
  //
29
+ // `shims`: which of the hosted hub's ways of working out where a thread stands
30
+ // are on (SHIM_DEFAULTS names them and their defaults); an unknown name is refused.
31
+ //
28
32
  // `code`: where the code this workspace's bots work on lives (absolute, ~/,
29
33
  // or relative to the workspace), when it isn't the workspace repo itself.
30
34
  // Every bot's bash tool names it (tools/bash.ts), so no bot has to hunt for it.
@@ -84,7 +88,7 @@ export function agentRunsOn(parsed, name) {
84
88
  return agent.runsOn ?? parsed.runsOn ?? "hosted";
85
89
  }
86
90
 
87
- // Validates the parsed file; returns { agents, runsOn?, humans?, subagentCap? } with
91
+ // Validates the parsed file; returns { agents, runsOn?, humans?, subagentCap?, hopLimit?, shims? } with
88
92
  // ports not yet assigned. Throws a WorkspaceError naming the bad field.
89
93
  export function parseWorkspace(data, file = WORKSPACE_FILE) {
90
94
  if (!isObject(data)) fail(file, "must be a JSON object with an \"agents\" array");
@@ -157,6 +161,10 @@ export function parseWorkspace(data, file = WORKSPACE_FILE) {
157
161
  if (hopLimitError(data.hopLimit)) fail(file, `"hopLimit" ${hopLimitError(data.hopLimit)}`);
158
162
  out.hopLimit = data.hopLimit;
159
163
  }
164
+ if (data.shims !== undefined) {
165
+ if (shimsError(data.shims)) fail(file, `"shims" ${shimsError(data.shims)}`);
166
+ out.shims = { ...data.shims };
167
+ }
160
168
  if (data.code !== undefined) {
161
169
  const code = typeof data.code === "string" ? [data.code] : data.code;
162
170
  if (!Array.isArray(code) || !code.length || code.some((p) => typeof p !== "string" || !p.trim())) fail(file, "\"code\" must be a path or a non-empty array of paths");
@@ -181,6 +189,25 @@ export const hopLimitError = (value) => (value === null || (Number.isInteger(val
181
189
  // The hop limit a parsed workspace sets: its `hopLimit`, else null (no limit).
182
190
  export const workspaceHopLimit = (parsed) => parsed.hopLimit ?? null;
183
191
 
192
+ // The hub's thread-state shims, each on or off in hub.workspace.json's `shims`, in the order the hub reads
193
+ // their answers when more than one is on; a name the file leaves out takes its default here.
194
+ export const SHIM_DEFAULTS = Object.freeze({ "thread-tracker": true, "thread-post-model": true, "thread-sitrep": false });
195
+
196
+ // Why `value` isn't a valid shims map (known shim names to booleans), or null when it is.
197
+ export const shimsError = (value) => {
198
+ if (!isObject(value)) return `must be an object of shim names to true or false (got ${JSON.stringify(value)})`;
199
+ const unknown = Object.keys(value).filter((name) => !(name in SHIM_DEFAULTS));
200
+ if (unknown.length) return `names unknown shims: ${unknown.join(", ")} (known: ${Object.keys(SHIM_DEFAULTS).join(", ")})`;
201
+ const bad = Object.entries(value).filter(([, on]) => typeof on !== "boolean");
202
+ return bad.length ? `must map each shim to true or false (${bad.map(([name]) => name).join(", ")})` : null;
203
+ };
204
+
205
+ // Every shim with whether it is on: the file's in its order, then the defaults of those it leaves out.
206
+ export const workspaceShims = (parsed) => {
207
+ const given = parsed?.shims ?? {};
208
+ return { ...given, ...Object.fromEntries(Object.entries(SHIM_DEFAULTS).filter(([name]) => !(name in given))) };
209
+ };
210
+
184
211
  // Deterministic ports: explicit `port`s are kept; every other agent gets the
185
212
  // first base port from `base` (in file order) that no agent has taken.
186
213
  // `offset` is added to every port.
@@ -1,10 +0,0 @@
1
- declare const _default: import("eve/tools").ToolDefinition<{
2
- scope: string;
3
- text: string;
4
- }, any> & {
5
- execute(input: {
6
- scope: string;
7
- text: string;
8
- }, ctx: import("eve/tools").ToolContext): Promise<any>;
9
- };
10
- export default _default;
@@ -1,25 +0,0 @@
1
- // The `set_summary` tool (main's): write the free-form summary of a path
2
- // (project/track) or a thread, one markdown note a model keeps current. A track's says what's up
3
- // in that feature (the human sees it above the track's threads); a thread's
4
- // says what it's about and where it stands. The classifier files threads by
5
- // these, and list_threads returns them. lib/paths.mjs setSummary.
6
- import { defineTool } from "eve/tools";
7
- import { z } from "zod";
8
- import { registryFetch } from "../registry-fetch.mjs";
9
- export default defineTool({
10
- description: "Write the summary of a path (project/track) or a thread: a short free-form markdown note, kept current. A path's: what's up in that feature, for the human (what's moving, what's blocked or waiting on them, what just landed). A thread's: what it's about and where it stands. You decide what's worth saying. It replaces the previous summary; empty text clears it. list_threads returns both.",
11
- inputSchema: z.object({
12
- scope: z.string().describe("What it summarizes: 'path:<path>' (e.g. 'path:web/checkout') or 'thread:<id>' (e.g. 'thread:thr_0d00')."),
13
- text: z.string().describe("The whole new summary, markdown. Empty clears it."),
14
- }),
15
- async execute({ scope, text }, ctx) {
16
- const res = await registryFetch(`/api/summaries`, {
17
- method: "POST",
18
- headers: { "content-type": "application/json" },
19
- body: JSON.stringify({ sessionId: ctx.session.id, scope, text }),
20
- });
21
- if (!res.ok)
22
- throw new Error(`set_summary failed: ${res.status} ${await res.text()}`);
23
- return await res.json();
24
- },
25
- });