eve-hub 0.6.41 → 0.6.43

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.6.43
4
+
5
+ ### Patch Changes
6
+
7
+ - Every bot is given the team model (who is on the team, where each runs, each bot's real tools from its eve runtime, who hands work to whom) as a memory slot that re-sends only when it changes, hosted bots included; `list_agents` is on every bot and reports each bot's kind, place and tools.
8
+
9
+ ## 0.6.42
10
+
11
+ ### Patch Changes
12
+
13
+ - The state frame carries each bot's latest finished turn per thread (`lastTurn`), so the app marks a thread failed from that turn.
14
+
3
15
  ## 0.6.41
4
16
 
5
17
  ### Patch Changes
@@ -39,15 +39,13 @@ export function deploymentFacts(env = process.env) {
39
39
 
40
40
  const list = (items) => items.map((item) => `\`${item}\``).join(", ");
41
41
 
42
- export function hostedContext({ bot, roster, kit, removed, deployment: d }, options) {
43
- const mac = roster.filter((agent) => agent.runsOn === "local").map((agent) => agent.name);
44
- const cloud = roster.filter((agent) => agent.runsOn !== "local").map((agent) => agent.name);
42
+ export function hostedContext({ bot, kit, removed, deployment: d }, options) {
45
43
  const dashboard = d.team && d.project ? `https://vercel.com/${d.team}/${d.project}` : undefined;
46
44
  const lines = [
47
45
  "## Execution context",
48
46
  `You are \`${bot}\`, an eve agent deployed on Vercel, not on a laptop: \`hub.workspace.json\` defines you as hosted.`,
49
47
  "",
50
- `- **Workspace:** ${cloud.length ? `${list(cloud)} run on Vercel` : "no bot runs on Vercel"}${mac.length ? `; ${list(mac)} run on the owner's Mac through the hub runner` : ""}. One hosted hub holds the threads and routes messages to each.`,
48
+ "- **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`.",
51
49
  `- **Package:** your tools, hooks, channel and instructions come from the \`eve-hub\` package, version ${kit}.`,
52
50
  `- **Deployment:** ${[
53
51
  (d.project ?? d.projectId) && `project \`${d.project ?? d.projectId}\``,
package/kit-modules.json CHANGED
@@ -8,6 +8,7 @@
8
8
  "instructions/main",
9
9
  "instructions/protocol",
10
10
  "memory/bot",
11
+ "memory/team",
11
12
  "memory/workspace",
12
13
  "schedules/compact",
13
14
  "schedules/dash",
@@ -0,0 +1,20 @@
1
+ declare const recall: ({ messages }: {
2
+ messages: readonly unknown[];
3
+ }) => Promise<{
4
+ messages: {
5
+ id: string;
6
+ content: string;
7
+ }[];
8
+ }>;
9
+ declare const _default: import("eve/memory").DefinedMemory<{
10
+ readonly description: "The team: who is on it, what each bot can do, and who hands work to whom.";
11
+ readonly namespace: "hub-team-memory";
12
+ readonly scope: "workspace";
13
+ readonly provider: {
14
+ readonly recall: {
15
+ "turn.started": typeof recall;
16
+ "compaction.completed": typeof recall;
17
+ };
18
+ };
19
+ }>;
20
+ export default _default;
package/memory/team.js ADDED
@@ -0,0 +1,38 @@
1
+ import { defineMemory, defineMemoryProvider } from "eve/memory";
2
+ import { registryFetch } from "../registry-fetch.mjs";
3
+ import { teamRecall } from "../team.mjs";
4
+ // The team model (team.mjs) as an eve memory slot: roster, each bot's tools
5
+ // and the handoff rules, from the hub's GET /api/team. Recall runs at the start
6
+ // of every turn and after compaction, and adds one message only when the team
7
+ // differs from what this session was last given, so a turn where nothing
8
+ // changed carries nothing. A hub that can't answer within 5 s fails the recall, not the turn.
9
+ const warned = new Set();
10
+ async function fetchTeam(since) {
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 }) => {
19
+ try {
20
+ return await teamRecall(messages, fetchTeam);
21
+ }
22
+ catch (error) {
23
+ const note = error.message;
24
+ if (!warned.has(note)) {
25
+ warned.add(note);
26
+ console.warn(`team: ${note}`);
27
+ }
28
+ return { messages: [] };
29
+ }
30
+ };
31
+ export default defineMemory({
32
+ description: "The team: who is on it, what each bot can do, and who hands work to whom.",
33
+ namespace: "hub-team-memory",
34
+ scope: "workspace",
35
+ provider: defineMemoryProvider({
36
+ recall: { "turn.started": recall, "compaction.completed": recall },
37
+ }),
38
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eve-hub",
3
- "version": "0.6.41",
3
+ "version": "0.6.43",
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
@@ -101,10 +101,10 @@ export default defineMemory({ namespace: ${JSON.stringify(`hub-${slot}-memory`)}
101
101
  // Replaces the kit's execution-context stub (the local page) with the hosted
102
102
  // page, for a bot the definition says is hosted. Its removed-tools list is read
103
103
  // from the bot's folder as pruned.
104
- function writeExecutionContext(bot, { bot: name, roster, deployment }) {
104
+ function writeExecutionContext(bot, { bot: name, deployment }) {
105
105
  const kitTools = Object.keys(name === "main" ? MAIN_KIT_FILES : KIT_FILES).filter((file) => file.startsWith("tools/") && file !== "tools/bash.ts").map((file) => basename(file, ".ts"));
106
106
  const removed = kitTools.filter((tool) => !existsSync(join(bot, "tools", `${tool}.ts`)));
107
- const section = hostedContext({ bot: name, roster, kit: KIT_VERSION, removed, deployment });
107
+ const section = hostedContext({ bot: name, kit: KIT_VERSION, removed, deployment });
108
108
  mkdirSync(join(bot, "instructions"), { recursive: true });
109
109
  writeFileSync(join(bot, "instructions", "execution-context.ts"), `import { defineInstructions } from "eve/instructions";\n\nexport default defineInstructions({ content: ${JSON.stringify(section)} });\n`);
110
110
  }
@@ -141,11 +141,10 @@ export function prepareDeployment(dir) {
141
141
  writeFileSync(join(bot, stub), content);
142
142
  }
143
143
  }
144
- const roster = resolved.agents;
145
144
  const deployment = deploymentFacts();
146
145
  for (const name of readdirSync(join(dir, "agents"))) {
147
146
  const bot = join(dir, "agents", name, "agent");
148
- writeExecutionContext(bot, { bot: name, roster, deployment });
147
+ writeExecutionContext(bot, { bot: name, deployment });
149
148
  // The kit's extension (kit__worker) runs host-only workers and is excluded from the kit tarball.
150
149
  // Other extensions, such as eve-code, stay.
151
150
  for (const item of readdirSync(bot, { recursive: true })) {
package/stubs.d.mts CHANGED
@@ -1,6 +1,7 @@
1
1
  export const KIT_FILES: Record<string, string[]>;
2
2
  export const MAIN_KIT_FILES: Record<string, string[]>;
3
3
  export const DEPLOYED_KIT_FILES: Set<string>;
4
+ export const MEMORY_STUBS: Record<string, string>;
4
5
  export const PROTOCOL_STUB_FILE: string;
5
6
  export const PROTOCOL_STUB: string;
6
7
  export function kitStub(file: string, options?: { files?: Record<string, string[]> }): string;
package/stubs.mjs CHANGED
@@ -14,6 +14,7 @@ export const KIT_FILES = {
14
14
  "tools/update_thread.ts": ["Update a thread this bot is in: title, track, domain and summary."],
15
15
  "tools/list_threads.ts": ["Every workspace thread, from any session."],
16
16
  "tools/read_thread.ts": ["Read any workspace thread."],
17
+ "tools/list_agents.ts": ["The team right now: kind, place, tools, models, who's busy."],
17
18
  "tools/post_message.ts": ["Post into an existing thread or a bot's DM; with `status`, report the thread's phase."],
18
19
  };
19
20
 
@@ -28,7 +29,6 @@ export const MAIN_KIT_FILES = {
28
29
  "tools/move_path.ts": ["Rename or merge a path (project/track): every thread under it moves."],
29
30
  "tools/archive_thread.ts": ["Archive a thread whose work is done."],
30
31
  "tools/unarchive_thread.ts": ["Bring an archived thread back."],
31
- "tools/list_agents.ts": ["The team right now: roles, models, who's busy."],
32
32
  "tools/create_agent.ts": ["Add a bot to the workspace."],
33
33
  "tools/remove_agent.ts": ["Remove a bot from the workspace."],
34
34
  "tools/restart_agent.ts": ["Restart one bot's server."],
@@ -61,6 +61,7 @@ export const DEPLOYED_KIT_FILES = new Set([
61
61
  "skills/onboarding-profile.ts",
62
62
  "tools/onboarding_scripts.ts",
63
63
  ...["react", "show", "update_thread", "list_threads", "read_thread", "post_message"].map((t) => `tools/${t}.ts`),
64
+ "memory/team.ts",
64
65
  ...["create_thread", "set_summary", "move_path", "archive_thread", "unarchive_thread", "list_agents", "agent_send", "create_agent", "remove_agent", "restart_agent", "tag_threads", "dash_update", "apply_tags"].map((t) => `tools/${t}.ts`),
65
66
  ]);
66
67
 
@@ -97,9 +98,10 @@ import workspace from "../../../../hub.workspace.json" with { type: "json" };
97
98
  export default defineInstructions({ content: executionContext(${JSON.stringify(name)}, workspace) });
98
99
  `;
99
100
 
100
- // Every bot (main too) has two memory slots: memory/workspace.ts recalls the
101
- // workspace's memory/ entries and memory/bot.ts the bot's own
102
- // agents/<bot>/memory/ entries, every turn (packages/agent-kit/memory/).
101
+ // Every bot (main too) has three memory slots: memory/workspace.ts recalls the
102
+ // workspace's memory/ entries, memory/bot.ts the bot's own agents/<bot>/memory/
103
+ // entries, and memory/team.ts the team model from the hub, every turn
104
+ // (packages/agent-kit/memory/).
103
105
  const memoryStub = (slot, what) => `// ${what}, recalled every turn. From eve-hub
104
106
  // (packages/agent-kit/memory/${slot}.ts); add or supersede entries there, not here.
105
107
  export { default } from "eve-hub/memory/${slot}";
@@ -107,6 +109,7 @@ export { default } from "eve-hub/memory/${slot}";
107
109
  export const MEMORY_STUBS = {
108
110
  "memory/workspace.ts": memoryStub("workspace", "The workspace's memory/ entries"),
109
111
  "memory/bot.ts": memoryStub("bot", "This bot's own agents/<bot>/memory/ entries"),
112
+ "memory/team.ts": memoryStub("team", "The team model (roster, tools, handoffs) from the hub"),
110
113
  };
111
114
 
112
115
  // main's instructions aren't copied into a workspace: agent/instructions.ts
package/sync-kit.mjs CHANGED
@@ -65,8 +65,8 @@ export function syncKit({ bots, modules }) {
65
65
  const wanted = new Set(Object.keys(stubs).filter((file) => local || DEPLOYED_KIT_FILES.has(file)));
66
66
  if (local) {
67
67
  wanted.add(EXECUTION_CONTEXT_STUB_FILE);
68
- for (const file of Object.keys(MEMORY_STUBS)) wanted.add(file);
69
68
  }
69
+ for (const file of Object.keys(MEMORY_STUBS)) if (local || DEPLOYED_KIT_FILES.has(file)) wanted.add(file);
70
70
  const content = (file) => (file === EXECUTION_CONTEXT_STUB_FILE ? executionContextStub(bot) : MEMORY_STUBS[file] ?? kitStub(file, { files: stubs }));
71
71
  for (const file of Object.keys(files).sort()) {
72
72
  const rel = file.replace(SUBAGENT, "");
package/team.d.mts ADDED
@@ -0,0 +1,24 @@
1
+ export type TeamNode = {
2
+ id: string;
3
+ kind: "human" | "coordinator" | "planner" | "builder" | "reviewer" | "git-ops" | "demo" | "other";
4
+ label: string;
5
+ color?: string;
6
+ role?: string;
7
+ model?: string;
8
+ runsOn?: "hosted" | "local";
9
+ tools?: string[] | null;
10
+ subagents?: string[];
11
+ };
12
+ export type TeamEdge = { from: string; to: string; kind: string };
13
+ export type TeamModel = { version: 1; hash: string; nodes: TeamNode[]; edges: TeamEdge[] };
14
+ export type TeamAgent = { name: string; role: string; model: string; color?: string; kind?: string; runsOn?: "hosted" | "local"; subagents?: Record<string, unknown> };
15
+ export const KINDS: readonly string[];
16
+ export const MODEL_VERSION: 1;
17
+ export const NODE_KINDS: readonly [string, ...string[]];
18
+ export const EDGE_KINDS: readonly [string, ...string[]];
19
+ export const SECTIONS: readonly { id: string; edge: { kind: string; from: unknown; to: unknown }; text: string }[];
20
+ export const SECTIONS_HASH: string;
21
+ export function teamEdges(nodes: readonly TeamNode[]): TeamEdge[];
22
+ export function teamModel(input: { agents: readonly TeamAgent[]; humans?: readonly { name: string; color?: string }[]; tools?: Record<string, readonly string[] | null | undefined> }): TeamModel;
23
+ export function teamText(model: TeamModel): string;
24
+ export function teamRecall(history: readonly unknown[], fetchTeam: (since?: string) => Promise<TeamModel | null>): Promise<{ messages: { id: string; content: string }[] }>;
package/team.mjs ADDED
@@ -0,0 +1,113 @@
1
+ // The team model: who is on the team, what each bot can do, and who hands work
2
+ // to whom. One derivation feeds every reader: the hub's GET /api/team (the
3
+ // native Team window, list_agents) and the prompt's team slot (memory/team.ts),
4
+ // which delivers it to a session once and again only when it changes.
5
+ //
6
+ // A bot's `kind` is its place in the team (hub.workspace.json). Its tools are
7
+ // what its running eve reports (/v1/info), never read from files. The edges
8
+ // come from SECTIONS: each protocol section names the edge it draws between
9
+ // kinds (or a tool), and the same entry renders the sentence the bots read.
10
+ import { createHash } from "node:crypto";
11
+ import { KINDS } from "./workspace.mjs";
12
+
13
+ export { KINDS };
14
+ export const MODEL_VERSION = 1;
15
+ export const NODE_KINDS = [...KINDS, "other", "human"];
16
+
17
+ const sha = (text, length) => createHash("sha256").update(text).digest("hex").slice(0, length);
18
+
19
+ // Selectors: "human", "bots" (every bot), { kind: [...] } or { tool: "agent_send" };
20
+ // all but the first pick bots. `text` is the sentence the team prompt carries when the section
21
+ // has at least one edge; `{from}` is the names behind the edge's from side.
22
+ export const SECTIONS = [
23
+ { id: "partner", edge: { kind: "partner", from: "human", to: { kind: ["coordinator"] } }, text: "The coordinator works with the human directly." },
24
+ { id: "route", edge: { kind: "route", from: { kind: ["coordinator"] }, to: { kind: ["planner", "builder"] } }, text: "The coordinator routes work to planners and builders." },
25
+ { id: "plan", edge: { kind: "plan", from: { kind: ["planner"] }, to: { kind: ["builder"] } }, text: "Planners hand plans to builders." },
26
+ { id: "build", edge: { kind: "build", from: { kind: ["builder"] }, to: { kind: ["reviewer"] } }, text: "Builders hand finished work to a reviewer." },
27
+ { id: "fixfirst", edge: { kind: "fixfirst", from: { kind: ["reviewer"] }, to: { kind: ["builder"] } }, text: "Reviewers send fixes back to the builder that posted the build." },
28
+ { id: "ship", edge: { kind: "ship", from: { kind: ["reviewer"] }, to: "human" }, text: "A reviewer's ship verdict goes to the human." },
29
+ { id: "escalate", edge: { kind: "escalate", from: { kind: ["git-ops"] }, to: { kind: ["builder"] } }, text: "Git-ops bots report what they can't fix to a builder." },
30
+ { id: "steer", edge: { kind: "steer", from: { tool: "agent_send" }, to: "bots" }, text: "Only {from} can queue work for or steer another bot (`agent_send`)." },
31
+ ];
32
+ export const EDGE_KINDS = SECTIONS.map((section) => section.edge.kind);
33
+ export const SECTIONS_HASH = sha(JSON.stringify(SECTIONS), 8);
34
+
35
+ const pick = (selector, nodes) =>
36
+ nodes.filter((n) => {
37
+ if (selector === "human") return n.kind === "human";
38
+ if (n.kind === "human") return false;
39
+ return selector === "bots" || (selector.kind ? selector.kind.includes(n.kind) : Boolean(n.tools?.includes(selector.tool)));
40
+ });
41
+
42
+ export function teamEdges(nodes) {
43
+ const edges = [];
44
+ for (const { edge } of SECTIONS) {
45
+ const from = pick(edge.from, nodes);
46
+ const to = pick(edge.to, nodes);
47
+ for (const f of from) for (const t of to) if (f.id !== t.id) edges.push({ from: f.id, to: t.id, kind: edge.kind });
48
+ }
49
+ return edges;
50
+ }
51
+
52
+ // `agents`: hub.workspace.json's agents plus `runsOn` resolved; `tools`: bot
53
+ // name -> the tool names its eve reports, absent or null when not reported.
54
+ export function teamModel({ agents, humans = [], tools = {} }) {
55
+ const nodes = [
56
+ ...humans.map((h) => ({ id: h.name, kind: "human", label: h.name, ...(h.color ? { color: h.color } : {}) })),
57
+ ...agents.map((a) => ({
58
+ id: a.name,
59
+ kind: a.kind ?? "other",
60
+ label: a.name,
61
+ ...(a.color ? { color: a.color } : {}),
62
+ role: a.role,
63
+ model: a.model,
64
+ runsOn: a.runsOn ?? "hosted",
65
+ tools: tools[a.name] ? [...new Set(tools[a.name])].sort() : null,
66
+ subagents: Object.keys(a.subagents ?? {}),
67
+ })),
68
+ ];
69
+ const edges = teamEdges(nodes);
70
+ return { version: MODEL_VERSION, hash: sha(JSON.stringify({ nodes, edges }), 12), nodes, edges };
71
+ }
72
+
73
+ const list = (items) => items.join(", ");
74
+
75
+ // What a session reads: the roster with each bot's kind, place and tools, then
76
+ // the handoff sentences. Identical for every bot, so one slot text serves the
77
+ // whole team. The header carries both hashes: the model's and the sections'.
78
+ export function teamText(model) {
79
+ const bots = model.nodes.filter((n) => n.kind !== "human");
80
+ const reported = bots.filter((b) => b.tools);
81
+ const shared = reported.length ? reported[0].tools.filter((t) => reported.every((b) => b.tools.includes(t))) : [];
82
+ const lines = [`Team model ${model.hash}/${SECTIONS_HASH}: the team right now, from the hub. It replaces any earlier team model above.`, "", "Bots:"];
83
+ for (const b of bots) {
84
+ const own = b.tools?.filter((t) => !shared.includes(t));
85
+ const where = b.runsOn === "local" ? "on the owner's Mac" : "on Vercel";
86
+ lines.push(`- ${b.id} (${b.kind}, ${where}): ${b.role}${own?.length ? ` Also: ${list(own)}.` : ""}${b.tools ? "" : " Tools not reported."}${b.subagents.length ? ` Subagents: ${list(b.subagents)}.` : ""}`);
87
+ }
88
+ if (shared.length) lines.push("", `Every bot with reported tools has: ${list(shared)}.`);
89
+ const rules = SECTIONS.flatMap(({ id, edge, text }) => {
90
+ const from = model.edges.filter((e) => e.kind === edge.kind).map((e) => e.from);
91
+ return from.length ? [`- ${text.replace("{from}", list([...new Set(from)]))}`] : [];
92
+ });
93
+ if (rules.length) lines.push("", "Handoffs:", ...rules);
94
+ return lines.join("\n");
95
+ }
96
+
97
+ const textOf = (m) => (typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((p) => (p?.type === "text" ? p.text : "")).join("") : "");
98
+ const HEADER = /^Team model ([0-9a-f]{12})\/([0-9a-f]{8}):/;
99
+
100
+ // The team slot's recall, given the session's history: the team model as one
101
+ // message when it differs from the last one this session was given, nothing
102
+ // otherwise. `fetchTeam(since)` asks the hub, with the model hash the session
103
+ // holds (only while these sections are the ones that rendered it), and answers
104
+ // the model, or null when the hub says nothing changed.
105
+ export async function teamRecall(history, fetchTeam) {
106
+ const given = history.filter((m) => m?.role === "user" && m.kind === "memory.load" && HEADER.test(textOf(m))).map(textOf);
107
+ const last = HEADER.exec(given.at(-1) ?? "");
108
+ const model = await fetchTeam(last && last[2] === SECTIONS_HASH ? last[1] : undefined);
109
+ if (!model) return { messages: [] };
110
+ const text = teamText(model);
111
+ if (given.at(-1) === text) return { messages: [] };
112
+ return { messages: [{ id: `team#${given.length + 1}@${sha(text, 8)}`, content: text }] };
113
+ }
@@ -2,6 +2,9 @@ declare const _default: import("eve/tools").ToolDefinition<Record<string, never>
2
2
  name: string;
3
3
  role: string;
4
4
  model: string;
5
+ kind?: string;
6
+ runsOn?: "hosted" | "local";
7
+ tools?: string[] | null;
5
8
  subagentCap: string;
6
9
  subagents: Array<{
7
10
  name: string;
@@ -31,6 +34,9 @@ declare const _default: import("eve/tools").ToolDefinition<Record<string, never>
31
34
  name: string;
32
35
  role: string;
33
36
  model: string;
37
+ kind?: string;
38
+ runsOn?: "hosted" | "local";
39
+ tools?: string[] | null;
34
40
  subagentCap: string;
35
41
  subagents: Array<{
36
42
  name: string;
@@ -2,7 +2,7 @@ import { defineTool } from "eve/tools";
2
2
  import { z } from "zod";
3
3
  import { registryFetch } from "../registry-fetch.mjs";
4
4
  export default defineTool({
5
- description: "List the workspace's bots right now: each one's role, model, the model and reasoning each of its declared subagents and its built-in `agent` copies resolve to under the workspace subagent cap (clamped from hub.workspace.json, never rejected), whether it's idle or busy (and where; a bot's threads run in parallel, so busy elsewhere doesn't mean it can't start work in a new thread), its server's memory (process.rssMb) and uptime (process.uptimeS; only on a local hub, since a hosted hub runs no bot process to measure), and any restart in progress (restart_agent, or the registry recycling a bot that grew too big or ran too long). The team changes; check it before routing work.",
5
+ description: "List the workspace's bots right now: each one's role, kind (its place in the team: coordinator, planner, builder, reviewer, git-ops, demo, or other), where it runs (hosted or local), the tools its runtime reports (null when not yet reported), its model, the model and reasoning each of its declared subagents and its built-in `agent` copies resolve to under the workspace subagent cap (clamped from hub.workspace.json, never rejected), whether it's idle or busy (and where; a bot's threads run in parallel, so busy elsewhere doesn't mean it can't start work in a new thread), its server's memory (process.rssMb) and uptime (process.uptimeS; only on a local hub, since a hosted hub runs no bot process to measure), and any restart in progress (restart_agent, or the registry recycling a bot that grew too big or ran too long). The team changes; check it before routing work.",
6
6
  inputSchema: z.object({}),
7
7
  async execute() {
8
8
  // Anything but a 2xx list of bots is an error naming the status and the
package/workspace.mjs CHANGED
@@ -47,9 +47,11 @@ export const DOMAIN = /^[a-z][a-z0-9-]*(\/[a-z][a-z0-9-]*)*$/;
47
47
  export const NAME = /^[a-z][a-z0-9-]{1,30}$/;
48
48
  export const HUMAN_NAME = /^[a-z][a-z0-9-]{0,30}$/;
49
49
  const COLOR = /^#[0-9a-f]{3}([0-9a-f]{3})?$/i;
50
- const AGENT_KEYS = new Set(["name", "model", "role", "color", "subagents", "subagentCap", "limits", "port", "runsOn", "domains"]);
50
+ const AGENT_KEYS = new Set(["name", "model", "role", "color", "subagents", "subagentCap", "limits", "port", "runsOn", "domains", "kind"]);
51
51
 
52
52
  export const RUNS_ON = ["hosted", "local"];
53
+ // The bot's place in the team (team.mjs draws the handoffs between kinds).
54
+ export const KINDS = ["coordinator", "planner", "builder", "reviewer", "git-ops", "demo"];
53
55
 
54
56
  export class WorkspaceError extends Error {}
55
57
 
@@ -102,6 +104,7 @@ export function parseWorkspace(data, file = WORKSPACE_FILE) {
102
104
  // Where the bot runs: on the hosted bots project (default), or on the human's Mac, started by
103
105
  // the app's local runner. Fixed at definition.
104
106
  if (agent.runsOn !== undefined && !RUNS_ON.includes(agent.runsOn)) fail(file, `${label}.runsOn must be "hosted" or "local"`);
107
+ if (agent.kind !== undefined && !KINDS.includes(agent.kind)) fail(file, `${label}.kind must be one of ${KINDS.join(", ")}`);
105
108
  if (agent.domains !== undefined && (!Array.isArray(agent.domains) || agent.domains.some((d) => typeof d !== "string" || !DOMAIN.test(d)))) fail(file, `${label}.domains must be an array of memory folders like "ui" or "ui/motion"`);
106
109
  const unknown = Object.keys(agent).filter((k) => !AGENT_KEYS.has(k));
107
110
  if (unknown.length) fail(file, `${label}: unknown field(s) ${unknown.join(", ")}`);
package/world.mjs CHANGED
@@ -417,316 +417,6 @@ function reduceChild(member, { type, data = {}, meta: meta4 }, at = Date.parse(m
417
417
  import { createClient } from "@libsql/client";
418
418
  var s = (sql, ...args) => ({ sql, args });
419
419
 
420
- // ../../hosted-hub/src/domain/ids.ts
421
- import { createHash, randomBytes } from "node:crypto";
422
- var alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
423
- function base32(value) {
424
- let out = "";
425
- for (let i = 0; i < 26; i++) {
426
- out = alphabet[Number(value & 31n)] + out;
427
- value >>= 5n;
428
- }
429
- return out;
430
- }
431
- function ulid(now = Date.now()) {
432
- return base32(BigInt(now) << 80n | BigInt(`0x${randomBytes(10).toString("hex")}`));
433
- }
434
- var eventId = () => `evt_${ulid()}`;
435
- function derivedMessageId(...parts) {
436
- return `msg_${base32(BigInt(`0x${createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 33)}`) >> 2n)}`;
437
- }
438
- var sessionId = () => `wrun_${ulid()}`;
439
- var deliveryId = (message, bot) => `dlv_${message}_${bot}`;
440
- var messageOfDelivery = (delivery, bot) => delivery.slice("dlv_".length, -`_${bot}`.length);
441
- var turnId = (session, chunkIndex) => `trn_${session}_${chunkIndex}`;
442
-
443
- // ../../hosted-hub/src/store/events.ts
444
- function eventStatements(threadId2, event, text, at, id = eventId(), only) {
445
- const expr = (v) => typeof v === "string" ? { sql: "?", args: [v] } : "sql" in v && "args" in v ? v : { sql: "?", args: [JSON.stringify(v)] };
446
- const [t, e] = [expr(text), expr(event)];
447
- return [
448
- s(
449
- `INSERT OR IGNORE INTO messages (id, thread_id, author, author_kind, text, created_at, event) SELECT ?, ?, 'system', 'event', ${t.sql}, ?, ${e.sql} WHERE ${only?.sql ?? "1"}`,
450
- id,
451
- threadId2,
452
- ...t.args,
453
- at,
454
- ...e.args,
455
- ...only?.args ?? []
456
- ),
457
- s(`INSERT INTO log (thread_id,type,data,at) SELECT ?, 'message.created', ?, ? WHERE changes() > 0`, threadId2, JSON.stringify({ threadId: threadId2, messageId: id, author: "system", authorKind: "event" }), at)
458
- ];
459
- }
460
-
461
- // ../../hosted-hub/src/store/routing.ts
462
- var THREAD_REFS = 3;
463
- var threadArgs = (threadId2, ...rest) => [...Array(THREAD_REFS).fill(threadId2), ...rest];
464
- var PARTICIPANTS = (thread, except = "''") => `SELECT DISTINCT p.bot FROM (
465
- SELECT thread_id AS tid, bot, created_at AS at, NULL AS mid FROM sessions WHERE thread_id = ${thread}
466
- UNION ALL SELECT thread_id, author, created_at, id FROM messages WHERE thread_id = ${thread} AND author_kind = 'agent'
467
- UNION ALL SELECT thread_id, json_extract(event, '$.agent'), created_at, id FROM messages WHERE thread_id = ${thread} AND author_kind = 'event' AND json_extract(event, '$.kind') = 'join') p
468
- WHERE p.mid IS NOT ${except} AND p.at > COALESCE((SELECT MAX(l.created_at) FROM messages l WHERE l.thread_id = p.tid AND l.author_kind = 'event'
469
- AND json_extract(l.event, '$.kind') = 'leave' AND json_extract(l.event, '$.agent') = p.bot), -1)`;
470
- var hasJoins = (threadId2) => !threadId2.startsWith("dm-") && !threadId2.startsWith("sch-");
471
- function routingStatements(messageId2, threadId2, bots, now, { by, joins }, held = {}, steer = false) {
472
- const quiet2 = !joins || !hasJoins(threadId2);
473
- const fresh = "EXISTS (SELECT 1 FROM messages WHERE id = ?)";
474
- return [
475
- ...bots.flatMap((bot) => [
476
- ...quiet2 ? [] : eventStatements(
477
- threadId2,
478
- { kind: "join", agent: bot, by, message: messageId2 },
479
- `@${bot} joined${by !== bot ? ` (added by @${by})` : ""}`,
480
- now,
481
- eventId(),
482
- s(`${fresh} AND NOT EXISTS (SELECT 1 FROM (${PARTICIPANTS("?")}) WHERE bot = ?)`, messageId2, ...threadArgs(threadId2), bot)
483
- ),
484
- s(`INSERT OR IGNORE INTO sessions (id,thread_id,bot,created_at) SELECT ?,?,?,? WHERE ${fresh}`, sessionId(), threadId2, bot, now, messageId2),
485
- s(
486
- `INSERT OR IGNORE INTO deliveries (id,message_id,bot,session_id,created_at)
487
- SELECT ?,?,?,id,? FROM sessions WHERE thread_id = ? AND bot = ? AND ended_at IS NULL AND ${fresh}`,
488
- deliveryId(messageId2, bot),
489
- messageId2,
490
- bot,
491
- now,
492
- threadId2,
493
- bot,
494
- messageId2
495
- ),
496
- ...steer ? [s("UPDATE deliveries SET steer = 1 WHERE id = ? AND accepted_at IS NULL", deliveryId(messageId2, bot))] : [],
497
- ...held[bot] ? [s("UPDATE deliveries SET held_at = ?, held_scope = ? WHERE id = ? AND accepted_at IS NULL", now, held[bot], deliveryId(messageId2, bot))] : []
498
- ])
499
- ];
500
- }
501
- var hopAfter = (session) => s(`(SELECT 1 + COALESCE(MAX(m.hop), 0) FROM deliveries d JOIN messages m ON m.id = d.message_id WHERE d.session_id = ? AND (
502
- EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = d.id AND td.turn_id = (SELECT id FROM turns WHERE session_id = ? ORDER BY started_at DESC, rowid DESC LIMIT 1))
503
- OR (d.accepted_at IS NOT NULL AND NOT EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = d.id))))`, session, session);
504
-
505
- // subagent-cap.mjs
506
- var DEFAULT_SUBAGENT_CAP = Object.freeze({
507
- model: "anthropic/claude-fable-5.1",
508
- reasoning: "low",
509
- models: Object.freeze(["anthropic/claude-fable-5.1"])
510
- });
511
-
512
- // ../../hosted-hub/src/store/roster.ts
513
- var onLocal = (bot) => `EXISTS (SELECT 1 FROM bots WHERE name = ${bot} AND runs_on = 'local')`;
514
- var HOP_LIMIT = "(SELECT json_extract(metadata, '$.workspace.hopLimit') FROM workspace_repo WHERE id = 1)";
515
- var heldByHopLimit = (m) => `(${m}.author_kind = 'agent' AND ${HOP_LIMIT} IS NOT NULL AND ${m}.hop >= ${HOP_LIMIT})`;
516
-
517
- // ../../hosted-hub/src/store/schema.ts
518
- var OPEN_TURN_STATUSES = "('streaming','cancelling')";
519
-
520
- // ../../hosted-hub/src/domain/wake.ts
521
- var escape = (s2) => s2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
522
- var mentioned = (roster, text) => roster.filter((bot) => new RegExp(`(^|[^\\w@])@${escape(bot)}(?![\\w-])`, "i").test(text));
523
- var hasAll = (text) => /(^|[^\w@])@all(?![\w-])/i.test(text);
524
- function recipients(roster, text, from, threadId2 = "", participants = []) {
525
- if (threadId2.startsWith("dm-")) {
526
- const bot = threadId2.slice(3);
527
- return bot !== from.name && roster.includes(bot) ? [bot] : [];
528
- }
529
- const named = [.../* @__PURE__ */ new Set([...mentioned(roster, text), ...hasAll(text) ? participants.filter((b) => roster.includes(b)) : []])].filter((bot) => bot !== from.name);
530
- if (from.kind === "agent") return named;
531
- return named.length ? named : roster.slice(0, 1);
532
- }
533
-
534
- // ../../lib/usage.mjs
535
- var emptyBucket = () => ({ steps: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costUsd: null, unpricedSteps: 0 });
536
- var TOKENS = ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"];
537
- var num = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
538
- var round = (v) => Math.round(v * 1e9) / 1e9;
539
- function addUsage(bucket, usage = {}) {
540
- bucket.steps += 1;
541
- for (const key of TOKENS) bucket[key] += num(usage?.[key]);
542
- const cost = usage?.costUsd;
543
- if (typeof cost === "number" && Number.isFinite(cost) && cost >= 0) bucket.costUsd = round((bucket.costUsd ?? 0) + cost);
544
- else bucket.unpricedSteps += 1;
545
- return bucket;
546
- }
547
- var HOURS_KEPT_MS = 35 * 24 * 36e5;
548
-
549
- // ../../hosted-hub/src/store/usage.ts
550
- var STEP_INSERT = "INSERT OR IGNORE INTO usage_steps (session_id, sequence, step_index, at, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, cost_usd, turn_id, model)";
551
- function stepValues(sessionId2, step) {
552
- const b = addUsage(emptyBucket(), step.usage);
553
- return [sessionId2, step.sequence, step.stepIndex, Date.parse(step.at), b.inputTokens, b.outputTokens, b.cacheReadTokens, b.cacheWriteTokens, b.costUsd];
554
- }
555
-
556
- // mention-gate.mjs
557
- function codeRanges(text) {
558
- const s2 = String(text ?? "");
559
- const ranges = [];
560
- const lines = [];
561
- for (let at = 0; at <= s2.length; ) {
562
- const nl = s2.indexOf("\n", at);
563
- const end = nl < 0 ? s2.length : nl;
564
- lines.push({ start: at, end, text: s2.slice(at, end) });
565
- if (nl < 0) break;
566
- at = nl + 1;
567
- }
568
- const fenced = /* @__PURE__ */ new Set();
569
- for (let i = 0; i < lines.length; i++) {
570
- const open3 = lines[i].text.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
571
- if (!open3 || open3[1][0] === "`" && open3[2].includes("`")) continue;
572
- const close = new RegExp(`^ {0,3}${open3[1][0] === "`" ? "`" : "~"}{${open3[1].length},}\\s*$`);
573
- const j = lines.findIndex((l, k2) => k2 > i && close.test(l.text));
574
- if (j < 0) continue;
575
- ranges.push([lines[i].start, lines[j].end]);
576
- for (let k2 = i; k2 <= j; k2++) fenced.add(k2);
577
- i = j;
578
- }
579
- let para = [];
580
- const flush = () => {
581
- if (!para.length) return;
582
- const from = para[0].start;
583
- const t = s2.slice(from, para.at(-1).end);
584
- const runs = [...t.matchAll(/`+/g)].map((m) => ({ at: m.index, n: m[0].length }));
585
- for (let i = 0; i < runs.length; i++) {
586
- let { at, n } = runs[i];
587
- let slash = 0;
588
- for (let b = at - 1; b >= 0 && t[b] === "\\"; b--) slash++;
589
- if (slash % 2 === 1) {
590
- at++;
591
- n--;
592
- if (!n) continue;
593
- }
594
- const j = runs.findIndex((r, k2) => k2 > i && r.n === n);
595
- if (j < 0) continue;
596
- ranges.push([from + at, from + runs[j].at + n]);
597
- i = j;
598
- }
599
- para = [];
600
- };
601
- lines.forEach((l, i) => {
602
- if (!fenced.has(i) && l.text.trim()) para.push(l);
603
- flush();
604
- });
605
- return ranges;
606
- }
607
-
608
- // mention-tokens.mjs
609
- function mentionTokens(text) {
610
- const s2 = String(text ?? "");
611
- const code = codeRanges(s2);
612
- const out = [];
613
- for (const match of s2.matchAll(/(?<![\w@./\\-])@([a-z0-9][a-z0-9-]*)/gi)) {
614
- if (!code.some(([a, b]) => match.index >= a && match.index < b)) out.push(match[1].toLowerCase());
615
- }
616
- return out;
617
- }
618
-
619
- // ../../hosted-hub/src/store/notifications.ts
620
- var NOTIFICATION_READS = s("SELECT person, notification_id FROM notification_reads");
621
- var NOTIFICATIONS = s("SELECT message_id, person FROM notifications");
622
- var names = (text, { person, names: names2 }) => {
623
- const known = new Set([person, ...names2].map((n) => n.toLowerCase()));
624
- return mentionTokens(text).some((t) => known.has(t));
625
- };
626
- var record = (id, person) => s("INSERT OR IGNORE INTO notifications (message_id, person) SELECT ?, ? WHERE EXISTS (SELECT 1 FROM messages WHERE id = ?)", id, person, id);
627
- var notificationFor = (messageId2, kind, text, notify) => notify && kind === "agent" && names(text, notify) ? [record(messageId2, notify.person)] : [];
628
-
629
- // ../../hosted-hub/src/dispatch/bot.ts
630
- var MESSAGE_LINE = "\n[thread ";
631
-
632
- // ../../hosted-hub/src/store/subagents.ts
633
- var UP = `up(session_id, parent) AS (
634
- SELECT session_id, parent FROM subagent_links WHERE session_id = ?
635
- UNION SELECT c.session_id, c.parent FROM subagent_links c JOIN up ON c.session_id = up.parent)`;
636
- var ROOT = "SELECT t.id, t.thread_id, t.bot FROM up JOIN turns t ON t.id = up.parent";
637
- var subagentStep = (sessionId2, step) => s(`WITH RECURSIVE ${UP} ${STEP_INSERT} SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, (SELECT id FROM (${ROOT})), NULL`, sessionId2, ...stepValues(sessionId2, step));
638
- var rootLog = (sessionId2, type, at) => s(`WITH RECURSIVE ${UP} INSERT INTO log (thread_id, type, data, at)
639
- SELECT thread_id, ?, json_object('threadId', thread_id, 'agent', bot, 'turnId', id), ? FROM (${ROOT})`, sessionId2, type, at);
640
- function linkStatements(sessionId2, parent, callId) {
641
- const subtree = `down(session_id) AS (SELECT ? UNION SELECT c.session_id FROM subagent_links c JOIN down ON c.parent = down.session_id)`;
642
- return [
643
- s("INSERT OR IGNORE INTO subagent_links (session_id, parent, call_id) VALUES (?, ?, ?)", sessionId2, parent, callId),
644
- s(`WITH RECURSIVE ${UP}, ${subtree} UPDATE usage_steps SET turn_id = (SELECT id FROM (${ROOT}))
645
- WHERE turn_id IS NULL AND session_id IN (SELECT session_id FROM down) AND EXISTS (${ROOT})`, sessionId2, sessionId2)
646
- ];
647
- }
648
-
649
- // ../../hosted-hub/src/store/failures.ts
650
- var STATE = "json_extract(event, '$.state')";
651
- var failureId = (trigger, bot) => `evt_fail_${trigger}_${bot}`;
652
- var cardLog = (idSql, now, ...args) => s(`INSERT INTO log (thread_id, type, data, at) SELECT thread_id, 'message.updated', json_object('threadId', thread_id, 'messageId', id), ? FROM messages WHERE id = (${idSql}) AND changes() > 0`, now, ...args);
653
- function failureStatements(t, now) {
654
- if (!t.trigger) return [];
655
- const id = failureId(t.trigger, t.bot), detail = t.error ?? "unknown error";
656
- const turn = (status) => s("EXISTS (SELECT 1 FROM turns WHERE id = ? AND status = ?)", t.id, status);
657
- const failed = turn("failed"), settled = (status, next) => {
658
- const when = turn(status);
659
- return [s(`UPDATE messages SET event = json_set(event, '$.state', ?) WHERE id = ? AND ${STATE} = 'retrying' AND ${when.sql}`, next, id, ...when.args), cardLog("?", now, id)];
660
- };
661
- return [
662
- ...eventStatements(t.threadId, { kind: "turn_failed", agent: t.bot, triggerId: t.trigger, state: "failed", retries: 0, turnId: t.id, detail }, `${t.bot}'s turn failed: ${detail}`, now, id, failed),
663
- s(`UPDATE messages SET text = ?, event = json_set(event, '$.state', 'failed', '$.detail', ?, '$.turnId', ?, '$.retries', json_extract(event, '$.retries') + 1)
664
- WHERE id = ? AND ${STATE} = 'retrying' AND json_extract(event, '$.turnId') <> ? AND ${failed.sql}`, `${t.bot}'s turn failed: ${detail}`, detail, t.id, id, t.id, ...failed.args),
665
- cardLog("?", now, id),
666
- ...settled("done", "resolved"),
667
- ...settled("cancelled", "failed")
668
- ];
669
- }
670
- var triggerOf = (t) => `(SELECT d.message_id FROM turn_deliveries td JOIN deliveries d ON d.id = td.delivery_id WHERE td.turn_id = ${t}.id ORDER BY d.rowid LIMIT 1)`;
671
-
672
- // ../../lib/tags.mjs
673
- var TAG_FIELDS = ["project", "issueType"];
674
- var LABEL_FIELDS = [...TAG_FIELDS, "path"];
675
-
676
- // ../../lib/subscriptions.mjs
677
- var STATUSES = Object.freeze(["idle", "working", "waiting"]);
678
-
679
- // ../../hosted-hub/src/store/dash.ts
680
- var COLUMNS = { importance: "importance", importanceBy: "importance_by", lane: "lane", delay: "delay", reason: "reason", basis: "basis", deferred: "deferred", updatedAt: "classified_at", by: "classified_by" };
681
- var FIELDS = Object.keys(COLUMNS);
682
- var CLASSIFICATION_COLUMNS = FIELDS.map((f) => COLUMNS[f]).join(", ");
683
- var DASH_READS = [s("SELECT * FROM dash_stamps")];
684
-
685
- // ../../hosted-hub/src/store/app.ts
686
- var APP_READS = [
687
- s("SELECT * FROM thread_meta"),
688
- s("SELECT * FROM read_states"),
689
- s("SELECT * FROM read_marks"),
690
- s("SELECT * FROM pins"),
691
- s("SELECT * FROM reactions WHERE active = 1"),
692
- s("SELECT * FROM human_profiles"),
693
- ...DASH_READS
694
- ];
695
-
696
- // ../../hosted-hub/src/store/requests.ts
697
- var SCHEDULE_LIMIT = `r.kind = 'session-limit' AND r.thread_id LIKE 'sch-%' AND NOT ${onLocal("r.bot")} AND r.sweep_gave_up_at IS NULL`;
698
- var PENDING_REQUESTS_READ = s(`SELECT r.id, r.session_id, r.thread_id, r.bot, r.kind, r.prompt, r.options, r.allow_freeform, r.tool_name, r.submitted_at, r.created_at,
699
- (SELECT d.message_id FROM turn_deliveries td JOIN deliveries d ON d.id = td.delivery_id WHERE td.turn_id = r.turn_id ORDER BY d.rowid DESC LIMIT 1) AS message_id
700
- FROM input_requests r JOIN sessions s ON s.id = r.session_id
701
- WHERE r.status = 'pending' AND s.ended_at IS NULL AND NOT (${SCHEDULE_LIMIT}) ORDER BY r.created_at`);
702
-
703
- // ../../hosted-hub/src/store/workspace-state.ts
704
- var LIVE_TURNS_READ = s(`SELECT thread_id,bot,started_at,reply_text,activity FROM turns WHERE status IN ${OPEN_TURN_STATUSES} ORDER BY started_at`);
705
-
706
- // ../../hosted-hub/src/store/attachments.ts
707
- var MAX_ATTACHMENTS = 10;
708
- var OPEN_TURN = `(SELECT id FROM turns WHERE session_id = ? AND status IN ${OPEN_TURN_STATUSES} ORDER BY started_at DESC LIMIT 1)`;
709
- var ATTACHMENTS_READ = s("SELECT message_id, upload_id, name FROM message_attachments ORDER BY message_id, position");
710
- var unseenBy = (bot, thread, mid) => {
711
- const end = `COALESCE((SELECT rowid FROM messages WHERE id = ${mid}), 9223372036854775807)`;
712
- return `a.message_id = e.id AND e.thread_id = ${thread} AND e.author <> ${bot} AND e.rowid < ${end}
713
- AND e.rowid > COALESCE((SELECT MAX(p.rowid) FROM deliveries dd JOIN messages p ON p.id = dd.message_id WHERE p.thread_id = e.thread_id AND dd.bot = ${bot} AND p.rowid < ${end}), 0)`;
714
- };
715
-
716
- // ../../hosted-hub/src/store/threads.ts
717
- var SUMMARY_READ = s("SELECT scope,text,by,at FROM summaries");
718
-
719
- // ../../lib/work-phases.mjs
720
- var PHASES = Object.freeze(["planning", "working", "reviewing", "done"]);
721
- var MOVES = Object.freeze({
722
- "": ["planning", "working"],
723
- planning: ["working"],
724
- working: ["reviewing"],
725
- reviewing: ["working", "planning", "done"],
726
- done: []
727
- });
728
- var VERDICTS = Object.freeze(["ship", "fix-first", "rethink"]);
729
-
730
420
  // ../../hosted-hub/node_modules/zod/v4/classic/external.js
731
421
  var external_exports = {};
732
422
  __export(external_exports, {
@@ -937,7 +627,7 @@ __export(external_exports, {
937
627
  properties: () => _properties,
938
628
  property: () => _property,
939
629
  readonly: () => readonly,
940
- record: () => record2,
630
+ record: () => record,
941
631
  refine: () => refine,
942
632
  regex: () => _regex,
943
633
  regexes: () => regexes_exports,
@@ -971,7 +661,7 @@ __export(external_exports, {
971
661
  tuple: () => tuple,
972
662
  uint32: () => uint32,
973
663
  uint64: () => uint64,
974
- ulid: () => ulid3,
664
+ ulid: () => ulid2,
975
665
  undefined: () => _undefined3,
976
666
  union: () => union,
977
667
  unknown: () => unknown,
@@ -2695,7 +2385,7 @@ __export(regexes_exports, {
2695
2385
  sha512_hex: () => sha512_hex,
2696
2386
  string: () => string,
2697
2387
  time: () => time,
2698
- ulid: () => ulid2,
2388
+ ulid: () => ulid,
2699
2389
  undefined: () => _undefined,
2700
2390
  unicodeEmail: () => unicodeEmail,
2701
2391
  uppercase: () => uppercase,
@@ -2707,7 +2397,7 @@ __export(regexes_exports, {
2707
2397
  });
2708
2398
  var cuid = /^[cC][0-9a-z]{6,}$/;
2709
2399
  var cuid2 = /^[0-9a-z]+$/;
2710
- var ulid2 = /^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/;
2400
+ var ulid = /^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/;
2711
2401
  var xid = /^[0-9a-vA-V]{20}$/;
2712
2402
  var ksuid = /^[A-Za-z0-9]{27}$/;
2713
2403
  var nanoid = /^[a-zA-Z0-9_-]{21}$/;
@@ -3646,7 +3336,7 @@ var $ZodCUID2 = /* @__PURE__ */ $constructor("$ZodCUID2", (inst, def) => {
3646
3336
  $ZodStringFormat.init(inst, def);
3647
3337
  });
3648
3338
  var $ZodULID = /* @__PURE__ */ $constructor("$ZodULID", (inst, def) => {
3649
- def.pattern ?? (def.pattern = ulid2);
3339
+ def.pattern ?? (def.pattern = ulid);
3650
3340
  $ZodStringFormat.init(inst, def);
3651
3341
  });
3652
3342
  var $ZodXID = /* @__PURE__ */ $constructor("$ZodXID", (inst, def) => {
@@ -5782,7 +5472,7 @@ var $ZodCyclicError = class extends Error {
5782
5472
  this.name = "ZodCyclicError";
5783
5473
  }
5784
5474
  };
5785
- var STATE2 = "~memo";
5475
+ var STATE = "~memo";
5786
5476
  var NO_ISSUES = [];
5787
5477
  function isRef(value) {
5788
5478
  return value !== null && typeof value === "object";
@@ -5996,10 +5686,10 @@ var memo = {
5996
5686
  const input2 = payload.value;
5997
5687
  if (!isRef(input2))
5998
5688
  return base(payload, ctx);
5999
- let state = ctx[STATE2];
5689
+ let state = ctx[STATE];
6000
5690
  if (!state) {
6001
5691
  state = { buckets: /* @__PURE__ */ new WeakMap(), backEdges: void 0 };
6002
- ctx[STATE2] = state;
5692
+ ctx[STATE] = state;
6003
5693
  }
6004
5694
  let bucket;
6005
5695
  if (lastCtx === ctx) {
@@ -6048,7 +5738,7 @@ function memoizer() {
6048
5738
  return memo;
6049
5739
  }
6050
5740
  function isBackEdge(ctx, value) {
6051
- const backEdges = ctx[STATE2]?.backEdges;
5741
+ const backEdges = ctx[STATE]?.backEdges;
6052
5742
  return backEdges !== void 0 && isRef(value) && backEdges.has(value);
6053
5743
  }
6054
5744
 
@@ -17849,7 +17539,7 @@ __export(schemas_exports2, {
17849
17539
  preprocess: () => preprocess,
17850
17540
  promise: () => promise,
17851
17541
  readonly: () => readonly,
17852
- record: () => record2,
17542
+ record: () => record,
17853
17543
  refine: () => refine,
17854
17544
  set: () => set,
17855
17545
  strictObject: () => strictObject,
@@ -17864,7 +17554,7 @@ __export(schemas_exports2, {
17864
17554
  tuple: () => tuple,
17865
17555
  uint32: () => uint32,
17866
17556
  uint64: () => uint64,
17867
- ulid: () => ulid3,
17557
+ ulid: () => ulid2,
17868
17558
  undefined: () => _undefined3,
17869
17559
  union: () => union,
17870
17560
  unknown: () => unknown,
@@ -18398,7 +18088,7 @@ var ZodULID = /* @__PURE__ */ $constructor("ZodULID", (inst, def) => {
18398
18088
  $ZodULID.init(inst, def);
18399
18089
  ZodStringFormat.init(inst, def);
18400
18090
  });
18401
- function ulid3(params) {
18091
+ function ulid2(params) {
18402
18092
  return _ulid(ZodULID, params);
18403
18093
  }
18404
18094
  var ZodXID = /* @__PURE__ */ $constructor("ZodXID", (inst, def) => {
@@ -18951,7 +18641,7 @@ var ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
18951
18641
  inst.keyType = def.keyType;
18952
18642
  inst.valueType = def.valueType;
18953
18643
  });
18954
- function record2(keyType, valueType, params) {
18644
+ function record(keyType, valueType, params) {
18955
18645
  if (!valueType || !valueType._zod) {
18956
18646
  return new ZodRecord({
18957
18647
  type: "record",
@@ -19426,7 +19116,7 @@ var stringbool = (...args) => _stringbool({
19426
19116
  }, ...args);
19427
19117
  function json(params) {
19428
19118
  const jsonSchema = lazy(() => {
19429
- return union([string2(params), number2(), boolean2(), _null3(), array(jsonSchema), record2(string2(), jsonSchema)]);
19119
+ return union([string2(params), number2(), boolean2(), _null3(), array(jsonSchema), record(string2(), jsonSchema)]);
19430
19120
  });
19431
19121
  return jsonSchema;
19432
19122
  }
@@ -20374,75 +20064,386 @@ function visit(schema, fnOrHandlers) {
20374
20064
  }
20375
20065
  }
20376
20066
  }
20377
- return run(schema);
20067
+ return run(schema);
20068
+ }
20069
+
20070
+ // ../../hosted-hub/node_modules/zod/v4/classic/deep-partial.js
20071
+ function deepPartial(schema) {
20072
+ return visit(schema, {
20073
+ object: (s2) => s2.partial(),
20074
+ // Every partialed option now admits `undefined`, which the constructor rejects as a duplicate.
20075
+ union: (s2) => {
20076
+ const def = s2._zod.def;
20077
+ return def.discriminator === void 0 ? s2 : union(def.options);
20078
+ }
20079
+ });
20080
+ }
20081
+
20082
+ // ../../hosted-hub/node_modules/zod/v4/classic/in-out.js
20083
+ function withChecks(side, checks) {
20084
+ if (!checks?.length)
20085
+ return side;
20086
+ const def = side._zod.def;
20087
+ return clone(side, mergeDefs(def, { checks: [...def.checks ?? [], ...checks] }), { parent: true });
20088
+ }
20089
+ function outSide(def) {
20090
+ return withChecks(def.out, def.checks);
20091
+ }
20092
+ function inSide(def) {
20093
+ return def.in._zod.traits.has("$ZodTransform") ? outSide(def) : def.in;
20094
+ }
20095
+ function input(schema) {
20096
+ return visit(schema, {
20097
+ pipe: (s2) => inSide(s2._zod.def),
20098
+ // A default value belongs to the output side, so a rewritten inner type leaves it stranded. `.default()` widens the declared input type with `undefined`, and `optional` is what carries that across.
20099
+ default: (s2, rewritten) => rewritten ? optional(s2._zod.def.innerType) : s2,
20100
+ // A catch value is output-side too, but `.catch()` leaves the declared input type alone, so the inner schema stands on its own.
20101
+ catch: (s2, rewritten) => rewritten ? s2._zod.def.innerType : s2
20102
+ });
20103
+ }
20104
+ function output(schema) {
20105
+ return visit(schema, {
20106
+ pipe: (s2) => outSide(s2._zod.def),
20107
+ // A prefault value is fed through the schema, which makes it input-side, so a rewritten inner type leaves it stranded.
20108
+ prefault: (s2, rewritten) => rewritten ? s2._zod.def.innerType : s2
20109
+ });
20110
+ }
20111
+
20112
+ // ../../hosted-hub/node_modules/zod/v4/classic/coerce.js
20113
+ var coerce_exports = {};
20114
+ __export(coerce_exports, {
20115
+ bigint: () => bigint3,
20116
+ boolean: () => boolean3,
20117
+ date: () => date4,
20118
+ number: () => number3,
20119
+ string: () => string3
20120
+ });
20121
+ function string3(params) {
20122
+ return _coercedString(ZodString, params);
20123
+ }
20124
+ function number3(params) {
20125
+ return _coercedNumber(ZodNumber, params);
20126
+ }
20127
+ function boolean3(params) {
20128
+ return _coercedBoolean(ZodBoolean, params);
20129
+ }
20130
+ function bigint3(params) {
20131
+ return _coercedBigint(ZodBigInt, params);
20132
+ }
20133
+ function date4(params) {
20134
+ return _coercedDate(ZodDate, params);
20135
+ }
20136
+
20137
+ // ../../hosted-hub/src/domain/ids.ts
20138
+ import { createHash, randomBytes } from "node:crypto";
20139
+ var alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
20140
+ function base32(value) {
20141
+ let out = "";
20142
+ for (let i = 0; i < 26; i++) {
20143
+ out = alphabet[Number(value & 31n)] + out;
20144
+ value >>= 5n;
20145
+ }
20146
+ return out;
20147
+ }
20148
+ function ulid3(now = Date.now()) {
20149
+ return base32(BigInt(now) << 80n | BigInt(`0x${randomBytes(10).toString("hex")}`));
20150
+ }
20151
+ var eventId = () => `evt_${ulid3()}`;
20152
+ function derivedMessageId(...parts) {
20153
+ return `msg_${base32(BigInt(`0x${createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 33)}`) >> 2n)}`;
20154
+ }
20155
+ var sessionId = () => `wrun_${ulid3()}`;
20156
+ var deliveryId = (message, bot) => `dlv_${message}_${bot}`;
20157
+ var messageOfDelivery = (delivery, bot) => delivery.slice("dlv_".length, -`_${bot}`.length);
20158
+ var turnId = (session, chunkIndex) => `trn_${session}_${chunkIndex}`;
20159
+
20160
+ // ../../hosted-hub/src/store/events.ts
20161
+ function eventStatements(threadId2, event, text, at, id = eventId(), only) {
20162
+ const expr = (v) => typeof v === "string" ? { sql: "?", args: [v] } : "sql" in v && "args" in v ? v : { sql: "?", args: [JSON.stringify(v)] };
20163
+ const [t, e] = [expr(text), expr(event)];
20164
+ return [
20165
+ s(
20166
+ `INSERT OR IGNORE INTO messages (id, thread_id, author, author_kind, text, created_at, event) SELECT ?, ?, 'system', 'event', ${t.sql}, ?, ${e.sql} WHERE ${only?.sql ?? "1"}`,
20167
+ id,
20168
+ threadId2,
20169
+ ...t.args,
20170
+ at,
20171
+ ...e.args,
20172
+ ...only?.args ?? []
20173
+ ),
20174
+ s(`INSERT INTO log (thread_id,type,data,at) SELECT ?, 'message.created', ?, ? WHERE changes() > 0`, threadId2, JSON.stringify({ threadId: threadId2, messageId: id, author: "system", authorKind: "event" }), at)
20175
+ ];
20176
+ }
20177
+
20178
+ // ../../hosted-hub/src/store/routing.ts
20179
+ var THREAD_REFS = 3;
20180
+ var threadArgs = (threadId2, ...rest) => [...Array(THREAD_REFS).fill(threadId2), ...rest];
20181
+ var PARTICIPANTS = (thread, except = "''") => `SELECT DISTINCT p.bot FROM (
20182
+ SELECT thread_id AS tid, bot, created_at AS at, NULL AS mid FROM sessions WHERE thread_id = ${thread}
20183
+ UNION ALL SELECT thread_id, author, created_at, id FROM messages WHERE thread_id = ${thread} AND author_kind = 'agent'
20184
+ UNION ALL SELECT thread_id, json_extract(event, '$.agent'), created_at, id FROM messages WHERE thread_id = ${thread} AND author_kind = 'event' AND json_extract(event, '$.kind') = 'join') p
20185
+ WHERE p.mid IS NOT ${except} AND p.at > COALESCE((SELECT MAX(l.created_at) FROM messages l WHERE l.thread_id = p.tid AND l.author_kind = 'event'
20186
+ AND json_extract(l.event, '$.kind') = 'leave' AND json_extract(l.event, '$.agent') = p.bot), -1)`;
20187
+ var hasJoins = (threadId2) => !threadId2.startsWith("dm-") && !threadId2.startsWith("sch-");
20188
+ function routingStatements(messageId2, threadId2, bots, now, { by, joins }, held = {}, steer = false) {
20189
+ const quiet2 = !joins || !hasJoins(threadId2);
20190
+ const fresh = "EXISTS (SELECT 1 FROM messages WHERE id = ?)";
20191
+ return [
20192
+ ...bots.flatMap((bot) => [
20193
+ ...quiet2 ? [] : eventStatements(
20194
+ threadId2,
20195
+ { kind: "join", agent: bot, by, message: messageId2 },
20196
+ `@${bot} joined${by !== bot ? ` (added by @${by})` : ""}`,
20197
+ now,
20198
+ eventId(),
20199
+ s(`${fresh} AND NOT EXISTS (SELECT 1 FROM (${PARTICIPANTS("?")}) WHERE bot = ?)`, messageId2, ...threadArgs(threadId2), bot)
20200
+ ),
20201
+ s(`INSERT OR IGNORE INTO sessions (id,thread_id,bot,created_at) SELECT ?,?,?,? WHERE ${fresh}`, sessionId(), threadId2, bot, now, messageId2),
20202
+ s(
20203
+ `INSERT OR IGNORE INTO deliveries (id,message_id,bot,session_id,created_at)
20204
+ SELECT ?,?,?,id,? FROM sessions WHERE thread_id = ? AND bot = ? AND ended_at IS NULL AND ${fresh}`,
20205
+ deliveryId(messageId2, bot),
20206
+ messageId2,
20207
+ bot,
20208
+ now,
20209
+ threadId2,
20210
+ bot,
20211
+ messageId2
20212
+ ),
20213
+ ...steer ? [s("UPDATE deliveries SET steer = 1 WHERE id = ? AND accepted_at IS NULL", deliveryId(messageId2, bot))] : [],
20214
+ ...held[bot] ? [s("UPDATE deliveries SET held_at = ?, held_scope = ? WHERE id = ? AND accepted_at IS NULL", now, held[bot], deliveryId(messageId2, bot))] : []
20215
+ ])
20216
+ ];
20217
+ }
20218
+ var hopAfter = (session) => s(`(SELECT 1 + COALESCE(MAX(m.hop), 0) FROM deliveries d JOIN messages m ON m.id = d.message_id WHERE d.session_id = ? AND (
20219
+ EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = d.id AND td.turn_id = (SELECT id FROM turns WHERE session_id = ? ORDER BY started_at DESC, rowid DESC LIMIT 1))
20220
+ OR (d.accepted_at IS NOT NULL AND NOT EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = d.id))))`, session, session);
20221
+
20222
+ // subagent-cap.mjs
20223
+ var DEFAULT_SUBAGENT_CAP = Object.freeze({
20224
+ model: "anthropic/claude-fable-5.1",
20225
+ reasoning: "low",
20226
+ models: Object.freeze(["anthropic/claude-fable-5.1"])
20227
+ });
20228
+
20229
+ // ../../hosted-hub/src/store/roster.ts
20230
+ var onLocal = (bot) => `EXISTS (SELECT 1 FROM bots WHERE name = ${bot} AND runs_on = 'local')`;
20231
+ var HOP_LIMIT = "(SELECT json_extract(metadata, '$.workspace.hopLimit') FROM workspace_repo WHERE id = 1)";
20232
+ var heldByHopLimit = (m) => `(${m}.author_kind = 'agent' AND ${HOP_LIMIT} IS NOT NULL AND ${m}.hop >= ${HOP_LIMIT})`;
20233
+ var ToolInfo = external_exports.object({ tools: external_exports.object({ static: external_exports.array(external_exports.object({ name: external_exports.string().min(1) })) }) });
20234
+
20235
+ // ../../hosted-hub/src/store/schema.ts
20236
+ var OPEN_TURN_STATUSES = "('streaming','cancelling')";
20237
+
20238
+ // ../../hosted-hub/src/domain/wake.ts
20239
+ var escape = (s2) => s2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
20240
+ var mentioned = (roster, text) => roster.filter((bot) => new RegExp(`(^|[^\\w@])@${escape(bot)}(?![\\w-])`, "i").test(text));
20241
+ var hasAll = (text) => /(^|[^\w@])@all(?![\w-])/i.test(text);
20242
+ function recipients(roster, text, from, threadId2 = "", participants = []) {
20243
+ if (threadId2.startsWith("dm-")) {
20244
+ const bot = threadId2.slice(3);
20245
+ return bot !== from.name && roster.includes(bot) ? [bot] : [];
20246
+ }
20247
+ const named = [.../* @__PURE__ */ new Set([...mentioned(roster, text), ...hasAll(text) ? participants.filter((b) => roster.includes(b)) : []])].filter((bot) => bot !== from.name);
20248
+ if (from.kind === "agent") return named;
20249
+ return named.length ? named : roster.slice(0, 1);
20378
20250
  }
20379
20251
 
20380
- // ../../hosted-hub/node_modules/zod/v4/classic/deep-partial.js
20381
- function deepPartial(schema) {
20382
- return visit(schema, {
20383
- object: (s2) => s2.partial(),
20384
- // Every partialed option now admits `undefined`, which the constructor rejects as a duplicate.
20385
- union: (s2) => {
20386
- const def = s2._zod.def;
20387
- return def.discriminator === void 0 ? s2 : union(def.options);
20388
- }
20389
- });
20252
+ // ../../lib/usage.mjs
20253
+ var emptyBucket = () => ({ steps: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costUsd: null, unpricedSteps: 0 });
20254
+ var TOKENS = ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"];
20255
+ var num = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
20256
+ var round = (v) => Math.round(v * 1e9) / 1e9;
20257
+ function addUsage(bucket, usage = {}) {
20258
+ bucket.steps += 1;
20259
+ for (const key of TOKENS) bucket[key] += num(usage?.[key]);
20260
+ const cost = usage?.costUsd;
20261
+ if (typeof cost === "number" && Number.isFinite(cost) && cost >= 0) bucket.costUsd = round((bucket.costUsd ?? 0) + cost);
20262
+ else bucket.unpricedSteps += 1;
20263
+ return bucket;
20390
20264
  }
20265
+ var HOURS_KEPT_MS = 35 * 24 * 36e5;
20391
20266
 
20392
- // ../../hosted-hub/node_modules/zod/v4/classic/in-out.js
20393
- function withChecks(side, checks) {
20394
- if (!checks?.length)
20395
- return side;
20396
- const def = side._zod.def;
20397
- return clone(side, mergeDefs(def, { checks: [...def.checks ?? [], ...checks] }), { parent: true });
20398
- }
20399
- function outSide(def) {
20400
- return withChecks(def.out, def.checks);
20401
- }
20402
- function inSide(def) {
20403
- return def.in._zod.traits.has("$ZodTransform") ? outSide(def) : def.in;
20404
- }
20405
- function input(schema) {
20406
- return visit(schema, {
20407
- pipe: (s2) => inSide(s2._zod.def),
20408
- // A default value belongs to the output side, so a rewritten inner type leaves it stranded. `.default()` widens the declared input type with `undefined`, and `optional` is what carries that across.
20409
- default: (s2, rewritten) => rewritten ? optional(s2._zod.def.innerType) : s2,
20410
- // A catch value is output-side too, but `.catch()` leaves the declared input type alone, so the inner schema stands on its own.
20411
- catch: (s2, rewritten) => rewritten ? s2._zod.def.innerType : s2
20412
- });
20267
+ // ../../hosted-hub/src/store/usage.ts
20268
+ var STEP_INSERT = "INSERT OR IGNORE INTO usage_steps (session_id, sequence, step_index, at, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, cost_usd, turn_id, model)";
20269
+ function stepValues(sessionId2, step) {
20270
+ const b = addUsage(emptyBucket(), step.usage);
20271
+ return [sessionId2, step.sequence, step.stepIndex, Date.parse(step.at), b.inputTokens, b.outputTokens, b.cacheReadTokens, b.cacheWriteTokens, b.costUsd];
20413
20272
  }
20414
- function output(schema) {
20415
- return visit(schema, {
20416
- pipe: (s2) => outSide(s2._zod.def),
20417
- // A prefault value is fed through the schema, which makes it input-side, so a rewritten inner type leaves it stranded.
20418
- prefault: (s2, rewritten) => rewritten ? s2._zod.def.innerType : s2
20273
+
20274
+ // mention-gate.mjs
20275
+ function codeRanges(text) {
20276
+ const s2 = String(text ?? "");
20277
+ const ranges = [];
20278
+ const lines = [];
20279
+ for (let at = 0; at <= s2.length; ) {
20280
+ const nl = s2.indexOf("\n", at);
20281
+ const end = nl < 0 ? s2.length : nl;
20282
+ lines.push({ start: at, end, text: s2.slice(at, end) });
20283
+ if (nl < 0) break;
20284
+ at = nl + 1;
20285
+ }
20286
+ const fenced = /* @__PURE__ */ new Set();
20287
+ for (let i = 0; i < lines.length; i++) {
20288
+ const open3 = lines[i].text.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
20289
+ if (!open3 || open3[1][0] === "`" && open3[2].includes("`")) continue;
20290
+ const close = new RegExp(`^ {0,3}${open3[1][0] === "`" ? "`" : "~"}{${open3[1].length},}\\s*$`);
20291
+ const j = lines.findIndex((l, k2) => k2 > i && close.test(l.text));
20292
+ if (j < 0) continue;
20293
+ ranges.push([lines[i].start, lines[j].end]);
20294
+ for (let k2 = i; k2 <= j; k2++) fenced.add(k2);
20295
+ i = j;
20296
+ }
20297
+ let para = [];
20298
+ const flush = () => {
20299
+ if (!para.length) return;
20300
+ const from = para[0].start;
20301
+ const t = s2.slice(from, para.at(-1).end);
20302
+ const runs = [...t.matchAll(/`+/g)].map((m) => ({ at: m.index, n: m[0].length }));
20303
+ for (let i = 0; i < runs.length; i++) {
20304
+ let { at, n } = runs[i];
20305
+ let slash = 0;
20306
+ for (let b = at - 1; b >= 0 && t[b] === "\\"; b--) slash++;
20307
+ if (slash % 2 === 1) {
20308
+ at++;
20309
+ n--;
20310
+ if (!n) continue;
20311
+ }
20312
+ const j = runs.findIndex((r, k2) => k2 > i && r.n === n);
20313
+ if (j < 0) continue;
20314
+ ranges.push([from + at, from + runs[j].at + n]);
20315
+ i = j;
20316
+ }
20317
+ para = [];
20318
+ };
20319
+ lines.forEach((l, i) => {
20320
+ if (!fenced.has(i) && l.text.trim()) para.push(l);
20321
+ flush();
20419
20322
  });
20323
+ return ranges;
20420
20324
  }
20421
20325
 
20422
- // ../../hosted-hub/node_modules/zod/v4/classic/coerce.js
20423
- var coerce_exports = {};
20424
- __export(coerce_exports, {
20425
- bigint: () => bigint3,
20426
- boolean: () => boolean3,
20427
- date: () => date4,
20428
- number: () => number3,
20429
- string: () => string3
20430
- });
20431
- function string3(params) {
20432
- return _coercedString(ZodString, params);
20433
- }
20434
- function number3(params) {
20435
- return _coercedNumber(ZodNumber, params);
20436
- }
20437
- function boolean3(params) {
20438
- return _coercedBoolean(ZodBoolean, params);
20326
+ // mention-tokens.mjs
20327
+ function mentionTokens(text) {
20328
+ const s2 = String(text ?? "");
20329
+ const code = codeRanges(s2);
20330
+ const out = [];
20331
+ for (const match of s2.matchAll(/(?<![\w@./\\-])@([a-z0-9][a-z0-9-]*)/gi)) {
20332
+ if (!code.some(([a, b]) => match.index >= a && match.index < b)) out.push(match[1].toLowerCase());
20333
+ }
20334
+ return out;
20439
20335
  }
20440
- function bigint3(params) {
20441
- return _coercedBigint(ZodBigInt, params);
20336
+
20337
+ // ../../hosted-hub/src/store/notifications.ts
20338
+ var NOTIFICATION_READS = s("SELECT person, notification_id FROM notification_reads");
20339
+ var NOTIFICATIONS = s("SELECT message_id, person FROM notifications");
20340
+ var names = (text, { person, names: names2 }) => {
20341
+ const known = new Set([person, ...names2].map((n) => n.toLowerCase()));
20342
+ return mentionTokens(text).some((t) => known.has(t));
20343
+ };
20344
+ var record2 = (id, person) => s("INSERT OR IGNORE INTO notifications (message_id, person) SELECT ?, ? WHERE EXISTS (SELECT 1 FROM messages WHERE id = ?)", id, person, id);
20345
+ var notificationFor = (messageId2, kind, text, notify) => notify && kind === "agent" && names(text, notify) ? [record2(messageId2, notify.person)] : [];
20346
+
20347
+ // ../../hosted-hub/src/dispatch/bot.ts
20348
+ var MESSAGE_LINE = "\n[thread ";
20349
+
20350
+ // ../../hosted-hub/src/store/subagents.ts
20351
+ var UP = `up(session_id, parent) AS (
20352
+ SELECT session_id, parent FROM subagent_links WHERE session_id = ?
20353
+ UNION SELECT c.session_id, c.parent FROM subagent_links c JOIN up ON c.session_id = up.parent)`;
20354
+ var ROOT = "SELECT t.id, t.thread_id, t.bot FROM up JOIN turns t ON t.id = up.parent";
20355
+ var subagentStep = (sessionId2, step) => s(`WITH RECURSIVE ${UP} ${STEP_INSERT} SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, (SELECT id FROM (${ROOT})), NULL`, sessionId2, ...stepValues(sessionId2, step));
20356
+ var rootLog = (sessionId2, type, at) => s(`WITH RECURSIVE ${UP} INSERT INTO log (thread_id, type, data, at)
20357
+ SELECT thread_id, ?, json_object('threadId', thread_id, 'agent', bot, 'turnId', id), ? FROM (${ROOT})`, sessionId2, type, at);
20358
+ function linkStatements(sessionId2, parent, callId) {
20359
+ const subtree = `down(session_id) AS (SELECT ? UNION SELECT c.session_id FROM subagent_links c JOIN down ON c.parent = down.session_id)`;
20360
+ return [
20361
+ s("INSERT OR IGNORE INTO subagent_links (session_id, parent, call_id) VALUES (?, ?, ?)", sessionId2, parent, callId),
20362
+ s(`WITH RECURSIVE ${UP}, ${subtree} UPDATE usage_steps SET turn_id = (SELECT id FROM (${ROOT}))
20363
+ WHERE turn_id IS NULL AND session_id IN (SELECT session_id FROM down) AND EXISTS (${ROOT})`, sessionId2, sessionId2)
20364
+ ];
20442
20365
  }
20443
- function date4(params) {
20444
- return _coercedDate(ZodDate, params);
20366
+
20367
+ // ../../hosted-hub/src/store/failures.ts
20368
+ var STATE2 = "json_extract(event, '$.state')";
20369
+ var failureId = (trigger, bot) => `evt_fail_${trigger}_${bot}`;
20370
+ var cardLog = (idSql, now, ...args) => s(`INSERT INTO log (thread_id, type, data, at) SELECT thread_id, 'message.updated', json_object('threadId', thread_id, 'messageId', id), ? FROM messages WHERE id = (${idSql}) AND changes() > 0`, now, ...args);
20371
+ function failureStatements(t, now) {
20372
+ if (!t.trigger) return [];
20373
+ const id = failureId(t.trigger, t.bot), detail = t.error ?? "unknown error";
20374
+ const turn = (status) => s("EXISTS (SELECT 1 FROM turns WHERE id = ? AND status = ?)", t.id, status);
20375
+ const failed = turn("failed"), settled = (status, next) => {
20376
+ const when = turn(status);
20377
+ return [s(`UPDATE messages SET event = json_set(event, '$.state', ?) WHERE id = ? AND ${STATE2} = 'retrying' AND ${when.sql}`, next, id, ...when.args), cardLog("?", now, id)];
20378
+ };
20379
+ return [
20380
+ ...eventStatements(t.threadId, { kind: "turn_failed", agent: t.bot, triggerId: t.trigger, state: "failed", retries: 0, turnId: t.id, detail }, `${t.bot}'s turn failed: ${detail}`, now, id, failed),
20381
+ s(`UPDATE messages SET text = ?, event = json_set(event, '$.state', 'failed', '$.detail', ?, '$.turnId', ?, '$.retries', json_extract(event, '$.retries') + 1)
20382
+ WHERE id = ? AND ${STATE2} = 'retrying' AND json_extract(event, '$.turnId') <> ? AND ${failed.sql}`, `${t.bot}'s turn failed: ${detail}`, detail, t.id, id, t.id, ...failed.args),
20383
+ cardLog("?", now, id),
20384
+ ...settled("done", "resolved"),
20385
+ ...settled("cancelled", "failed")
20386
+ ];
20445
20387
  }
20388
+ var triggerOf = (t) => `(SELECT d.message_id FROM turn_deliveries td JOIN deliveries d ON d.id = td.delivery_id WHERE td.turn_id = ${t}.id ORDER BY d.rowid LIMIT 1)`;
20389
+
20390
+ // ../../lib/tags.mjs
20391
+ var TAG_FIELDS = ["project", "issueType"];
20392
+ var LABEL_FIELDS = [...TAG_FIELDS, "path"];
20393
+
20394
+ // ../../lib/subscriptions.mjs
20395
+ var STATUSES = Object.freeze(["idle", "working", "waiting"]);
20396
+
20397
+ // ../../hosted-hub/src/store/dash.ts
20398
+ var COLUMNS = { importance: "importance", importanceBy: "importance_by", lane: "lane", delay: "delay", reason: "reason", basis: "basis", deferred: "deferred", updatedAt: "classified_at", by: "classified_by" };
20399
+ var FIELDS = Object.keys(COLUMNS);
20400
+ var CLASSIFICATION_COLUMNS = FIELDS.map((f) => COLUMNS[f]).join(", ");
20401
+ var DASH_READS = [s("SELECT * FROM dash_stamps")];
20402
+
20403
+ // ../../hosted-hub/src/store/app.ts
20404
+ var APP_READS = [
20405
+ s("SELECT * FROM thread_meta"),
20406
+ s("SELECT * FROM read_states"),
20407
+ s("SELECT * FROM read_marks"),
20408
+ s("SELECT * FROM pins"),
20409
+ s("SELECT * FROM reactions WHERE active = 1"),
20410
+ s("SELECT * FROM human_profiles"),
20411
+ ...DASH_READS
20412
+ ];
20413
+
20414
+ // ../../hosted-hub/src/store/requests.ts
20415
+ var SCHEDULE_LIMIT = `r.kind = 'session-limit' AND r.thread_id LIKE 'sch-%' AND NOT ${onLocal("r.bot")} AND r.sweep_gave_up_at IS NULL`;
20416
+ var PENDING_REQUESTS_READ = s(`SELECT r.id, r.session_id, r.thread_id, r.bot, r.kind, r.prompt, r.options, r.allow_freeform, r.tool_name, r.submitted_at, r.created_at,
20417
+ (SELECT d.message_id FROM turn_deliveries td JOIN deliveries d ON d.id = td.delivery_id WHERE td.turn_id = r.turn_id ORDER BY d.rowid DESC LIMIT 1) AS message_id
20418
+ FROM input_requests r JOIN sessions s ON s.id = r.session_id
20419
+ WHERE r.status = 'pending' AND s.ended_at IS NULL AND NOT (${SCHEDULE_LIMIT}) ORDER BY r.created_at`);
20420
+
20421
+ // ../../hosted-hub/src/store/workspace-state.ts
20422
+ var LIVE_TURNS_READ = s(`SELECT thread_id,bot,started_at,reply_text,activity FROM turns WHERE status IN ${OPEN_TURN_STATUSES} ORDER BY started_at`);
20423
+
20424
+ // ../../hosted-hub/src/store/attachments.ts
20425
+ var MAX_ATTACHMENTS = 10;
20426
+ var OPEN_TURN = `(SELECT id FROM turns WHERE session_id = ? AND status IN ${OPEN_TURN_STATUSES} ORDER BY started_at DESC LIMIT 1)`;
20427
+ var ATTACHMENTS_READ = s("SELECT message_id, upload_id, name FROM message_attachments ORDER BY message_id, position");
20428
+ var unseenBy = (bot, thread, mid) => {
20429
+ const end = `COALESCE((SELECT rowid FROM messages WHERE id = ${mid}), 9223372036854775807)`;
20430
+ return `a.message_id = e.id AND e.thread_id = ${thread} AND e.author <> ${bot} AND e.rowid < ${end}
20431
+ AND e.rowid > COALESCE((SELECT MAX(p.rowid) FROM deliveries dd JOIN messages p ON p.id = dd.message_id WHERE p.thread_id = e.thread_id AND dd.bot = ${bot} AND p.rowid < ${end}), 0)`;
20432
+ };
20433
+
20434
+ // ../../hosted-hub/src/store/threads.ts
20435
+ var SUMMARY_READ = s("SELECT scope,text,by,at FROM summaries");
20436
+
20437
+ // ../../lib/work-phases.mjs
20438
+ var PHASES = Object.freeze(["planning", "working", "reviewing", "done"]);
20439
+ var MOVES = Object.freeze({
20440
+ "": ["planning", "working"],
20441
+ planning: ["working"],
20442
+ working: ["reviewing"],
20443
+ reviewing: ["working", "planning", "done"],
20444
+ done: []
20445
+ });
20446
+ var VERDICTS = Object.freeze(["ship", "fix-first", "rethink"]);
20446
20447
 
20447
20448
  // ../../node_modules/.pnpm/zod@4.5.4/node_modules/zod/v4/core/util.js
20448
20449
  var util_exports2 = {};
@@ -26785,6 +26786,8 @@ var HubThread = object2({
26785
26786
  status: maybe(map2("ThreadStatus", string5())),
26786
26787
  busySince: maybe(map2("ThreadBusySince", string5())),
26787
26788
  turnCounts: maybe(map2("ThreadTurnCounts", int2())),
26789
+ /** Each bot's latest finished turn here: done | failed | cancelled. */
26790
+ lastTurn: maybe(map2("ThreadLastTurn", string5())),
26788
26791
  archived: maybe(Archived),
26789
26792
  source: maybe(ThreadSource),
26790
26793
  tags: maybe(ThreadTags),