eve-hub 0.8.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/kit-modules.json +0 -1
- package/mention-gate.d.mts +0 -1
- package/mention-gate.mjs +4 -56
- package/package.json +1 -1
- package/stubs.mjs +0 -2
- package/world.mjs +258 -105
- package/hooks/mention-gate.d.ts +0 -2
- package/hooks/mention-gate.js +0 -31
package/CHANGELOG.md
CHANGED
package/kit-modules.json
CHANGED
package/mention-gate.d.mts
CHANGED
|
@@ -2,5 +2,4 @@ export type GateConfig = { enabled: boolean; model: string; api: 'chat' | 'decis
|
|
|
2
2
|
export function gateConfig(env?: Record<string, string | undefined>, file?: unknown): GateConfig;
|
|
3
3
|
export function opensWithHandle(text: string, agent: string): boolean;
|
|
4
4
|
export function cleanReason(reason: unknown): string;
|
|
5
|
-
export function screenInbound(input: { text: string; sessionId: string; agent?: string; registryUrl: string; credential: (opts?: { refresh?: boolean }) => Promise<{ token: string; teamId?: string | null } | null>; instructions?: string; fetchImpl?: typeof fetch; log?: ((line: string) => void) | null }): Promise<{ cancel: boolean; result?: unknown }>;
|
|
6
5
|
export function hubCredentialLoader(fetchHub: (path: string, init: { headers?: Record<string, string> }) => Promise<Response>): (opts?: { refresh?: boolean }) => Promise<{ ok: boolean; token?: string; error?: string }>;
|
package/mention-gate.mjs
CHANGED
|
@@ -1,19 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// just received. A handle inside code (codeRanges) is dropped by
|
|
6
|
-
// parseMentions before it joins or wakes anyone; the gate catches the other
|
|
7
|
-
// incidental ones: a quote or a recap, or an ack that asks nothing.
|
|
8
|
-
//
|
|
9
|
-
// The hub decides which wakes are gated (gatedWake, registry/server.mjs) and
|
|
10
|
-
// owns the outcome: the hook asks it first (POST /api/mention-gate/check),
|
|
11
|
-
// then reports the decision (POST /api/mention-gate/report); on a skip the
|
|
12
|
-
// hub posts the system line and records the usage, and the hook cancels the
|
|
13
|
-
// turn. Biased to answering: the model only classifies the message (how the
|
|
14
|
-
// handle is used, what it asks of the bot); skipFor decides, and any error,
|
|
15
|
-
// timeout or unreadable verdict answers. Only bots' messages are gated: the
|
|
16
|
-
// human's always wake.
|
|
1
|
+
// The decision-model helpers the registry's mention gate (registry/server.mjs, frozen until its removal) and the hosted hub's wake
|
|
2
|
+
// router and closing check share: the gateway config (gateConfig), the Mac bot credential loader and the response parsing. The bot-side
|
|
3
|
+
// hook is gone; the hosted hub decides who a message wakes (hosted-hub/src/store/wake-router.ts). A handle inside code (codeRanges)
|
|
4
|
+
// is dropped by parseMentions before it joins or wakes anyone.
|
|
17
5
|
//
|
|
18
6
|
// Config (the hub's, sent to the hook with each check): `mentionGate` in
|
|
19
7
|
// hub.workspace.json ({ enabled, model, reasoningEffort, timeoutMs }),
|
|
@@ -21,7 +9,6 @@
|
|
|
21
9
|
// HUB_MENTION_GATE_REASONING, HUB_MENTION_GATE_TIMEOUT_MS and
|
|
22
10
|
// HUB_MENTION_GATE_URL (tests).
|
|
23
11
|
import { codeRanges } from "./mention-tokens.mjs";
|
|
24
|
-
import { hubHeaders } from "./registry-fetch.mjs";
|
|
25
12
|
import { createHash } from "node:crypto";
|
|
26
13
|
|
|
27
14
|
export { codeRanges };
|
|
@@ -395,45 +382,6 @@ export function credentialCache(load, { ttlMs = 10 * 60e3, failMs = 60e3, now =
|
|
|
395
382
|
return get;
|
|
396
383
|
}
|
|
397
384
|
|
|
398
|
-
// --- The bot's side (hooks/mention-gate.ts).
|
|
399
|
-
|
|
400
|
-
const HUB_CALL_MS = 2000;
|
|
401
|
-
|
|
402
|
-
async function hubCall(fetchImpl, url, body) {
|
|
403
|
-
const res = await fetchImpl(url, { method: "POST", headers: await hubHeaders({ "content-type": "application/json" }), body: JSON.stringify(body), signal: AbortSignal.timeout(HUB_CALL_MS) });
|
|
404
|
-
if (!res.ok) throw new Error(`hub ${res.status}`);
|
|
405
|
-
return res.json();
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// One inbound delivery, screened. Never throws; { cancel: true } only when the
|
|
409
|
-
// hub accepted a skip (it posted the system line and expects the turn to
|
|
410
|
-
// end cancelled). Anything that fails on the way answers.
|
|
411
|
-
// text: the delivery (message.received's data.message)
|
|
412
|
-
// credential: ({ refresh }) → { token, teamId? } | null (credentialCache)
|
|
413
|
-
// `agent` is optional: left out, the hub names the bot from its session and sends its instructions.
|
|
414
|
-
export async function screenInbound({ text, sessionId, agent, registryUrl, instructions = "", credential, fetchImpl = fetch, log = console.warn }) {
|
|
415
|
-
const messageId = triggerId(text);
|
|
416
|
-
if (!registryUrl || !messageId) return { cancel: false };
|
|
417
|
-
let check;
|
|
418
|
-
try {
|
|
419
|
-
check = await hubCall(fetchImpl, `${registryUrl}/api/mention-gate/check`, { ...(agent ? { agent } : {}), messageId, sessionId });
|
|
420
|
-
} catch (error) {
|
|
421
|
-
log?.(`[mention-gate] check for ${messageId} failed, answering: ${error?.message ?? error}`);
|
|
422
|
-
return { cancel: false };
|
|
423
|
-
}
|
|
424
|
-
if (!check?.gate || !check.config) return { cancel: false };
|
|
425
|
-
agent ??= check.agent;
|
|
426
|
-
instructions ||= check.instructions ?? "";
|
|
427
|
-
const r = await decide({ config: check.config, credential, prompt: promptFor(check.config, { agent, human: check.humanName ?? "the human", instructions, envelope: text, broadcast: Boolean(check.broadcast) }), addressed: Boolean(check.addressed), fetchImpl });
|
|
428
|
-
try {
|
|
429
|
-
const ack = await hubCall(fetchImpl, `${registryUrl}/api/mention-gate/report`, { ...(agent ? { agent } : {}), messageId, sessionId, skip: r.skip, reason: r.reason, verdict: r.verdict ?? null, usage: r.usage ?? null, ms: r.ms, error: r.error ?? null });
|
|
430
|
-
return { cancel: Boolean(r.skip && ack?.skip), result: r };
|
|
431
|
-
} catch (error) {
|
|
432
|
-
log?.(`[mention-gate] report for ${messageId} failed, answering: ${error?.message ?? error}`);
|
|
433
|
-
return { cancel: false, result: r };
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
385
|
// A Mac bot's AI Gateway credential: the hub mints an OIDC token for the workspace's bots project, asked for with the bot's HUB_TOKEN.
|
|
438
386
|
// `fetchHub(path, init)` is registryFetch. On a refresh it names the token AI Gateway refused, as eve's served credential does.
|
|
439
387
|
export function hubCredentialLoader(fetchHub) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eve-hub",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Bot-side code shared by every hub agent: registry tools, the roster hook, the eve channel, the host sandbox and browser, and model helpers.",
|
|
6
6
|
"type": "module",
|
package/stubs.mjs
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
export const KIT_FILES = {
|
|
5
5
|
"channels/eve.ts": ["Queued eve channel that resolves the registry's human as a user.", "channels/eve-roster-user.ts"],
|
|
6
6
|
"hooks/sandbox.ts": ["Migrates legacy Docker sessions to the host sandbox."],
|
|
7
|
-
"hooks/mention-gate.ts": ["Mention gate: a cheap model decides whether an inbound @-mention or DM needs a turn."],
|
|
8
7
|
"tools/bash.ts": ["eve's bash with a default time limit (kills the process group) and the workspace's code location."],
|
|
9
8
|
"tools/react.ts": ["React to a message with an emoji."],
|
|
10
9
|
"tools/show.ts": ["Move the human's screen to a thread, DM, or message."],
|
|
@@ -52,7 +51,6 @@ export const MAIN_KIT_FILES = {
|
|
|
52
51
|
// main's dash schedule stays: off the hub's machine it has no PR data (its lookup runs `gh`).
|
|
53
52
|
export const DEPLOYED_KIT_FILES = new Set([
|
|
54
53
|
"channels/eve.ts",
|
|
55
|
-
"hooks/mention-gate.ts",
|
|
56
54
|
"schedules/dash.ts",
|
|
57
55
|
"skills/onboarding-profile.ts",
|
|
58
56
|
"tools/onboarding_scripts.ts",
|
package/world.mjs
CHANGED
|
@@ -21397,6 +21397,12 @@ async function afterAppend(append, routed, fetchFn, env = process.env) {
|
|
|
21397
21397
|
// ../../hosted-hub/bots/world.ts
|
|
21398
21398
|
var import_oidc = __toESM(require_oidc(), 1);
|
|
21399
21399
|
|
|
21400
|
+
// ../../hosted-hub/src/store/db.ts
|
|
21401
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
21402
|
+
import { createClient, LibsqlError } from "@libsql/client";
|
|
21403
|
+
var s = (sql, ...args) => ({ sql, args });
|
|
21404
|
+
var running = new AsyncLocalStorage();
|
|
21405
|
+
|
|
21400
21406
|
// mention-tokens.mjs
|
|
21401
21407
|
function codeRanges(text) {
|
|
21402
21408
|
const s2 = String(text ?? "");
|
|
@@ -21498,90 +21504,144 @@ function gateConfig(env = process.env, file2 = {}) {
|
|
|
21498
21504
|
};
|
|
21499
21505
|
}
|
|
21500
21506
|
|
|
21501
|
-
// ../../hosted-hub/src/store/db.ts
|
|
21502
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
21503
|
-
import { createClient, LibsqlError } from "@libsql/client";
|
|
21504
|
-
var s = (sql, ...args) => ({ sql, args });
|
|
21505
|
-
var running = new AsyncLocalStorage();
|
|
21506
|
-
|
|
21507
21507
|
// ../../hosted-hub/src/store/schema.ts
|
|
21508
21508
|
var OPEN_TURN_STATUSES = "('streaming','cancelling')";
|
|
21509
21509
|
var UNSETTLED_DELIVERIES = "unsettled AS MATERIALIZED (SELECT rowid AS rid, * FROM deliveries WHERE accepted_at IS NULL AND failed_at IS NULL AND revoked_at IS NULL)";
|
|
21510
21510
|
|
|
21511
|
-
// ../../hosted-hub/src/
|
|
21512
|
-
|
|
21513
|
-
var
|
|
21514
|
-
|
|
21515
|
-
|
|
21516
|
-
|
|
21517
|
-
|
|
21518
|
-
|
|
21519
|
-
}
|
|
21520
|
-
|
|
21511
|
+
// ../../hosted-hub/src/store/wake-router.ts
|
|
21512
|
+
var GATE = gateConfig(process.env);
|
|
21513
|
+
var WAKE_AT = 0.15;
|
|
21514
|
+
var STEER_AT = 0.5;
|
|
21515
|
+
var CLIP = 700;
|
|
21516
|
+
var clip = (t, n = CLIP) => t.length > n ? `${t.slice(0, n)} \u2026` : t;
|
|
21517
|
+
var BOTS_BUSY = `SELECT b.name, b.role, EXISTS (SELECT 1 FROM turns t WHERE t.thread_id = ? AND t.bot = b.name AND t.status IN ${OPEN_TURN_STATUSES}) AS busy FROM bots b`;
|
|
21518
|
+
var WAKE_QUESTION = {
|
|
21519
|
+
named: (bot) => `The message @-mentions ${bot}. Decide whether ${bot} has something to do after reading it. Yes if it asks ${bot} for anything, hands ${bot} work or a result to review, land or build on, or gives ${bot} a fix, correction or answer ${bot} was waiting on. No if it only thanks or acknowledges, says it reacted or changed nothing, says nothing is needed from ${bot} or that the matter is already done and nobody has to act, says to ignore it, or only names ${bot} in a quote, a recap or an example.`,
|
|
21520
|
+
all: (bot) => `The message is addressed to everyone with @all. Does it need ${bot} to act, given ${bot}'s role? Yes if it asks for something ${bot} can give, or announces something ${bot} must act on. No if it is news, or for other bots' roles. A missed request stalls the thread: when unsure, yes.`,
|
|
21521
|
+
reply: (bot) => `The message does not @-mention ${bot}, but replies to ${bot}'s earlier request. Does it need ${bot} to act? Yes if it hands back an answer, result or approval ${bot} asked for, or asks it something. No if it only thanks or acknowledges, says it reacted, posted nothing new or has nothing to do, says the matter is already handled or nothing is needed from ${bot}, or only points back to messages ${bot} has already seen. A missed answer stalls the thread: when unsure, yes.`
|
|
21522
|
+
};
|
|
21523
|
+
var isPick = (bots) => bots.length > 0 && bots.every((b) => b.relation === "pick");
|
|
21524
|
+
function pickPrompt({ author, text, tail, bots }) {
|
|
21525
|
+
const input2 = [
|
|
21526
|
+
`${author} wrote a message in a team chat thread with the bots below.`,
|
|
21527
|
+
"",
|
|
21528
|
+
"Bots in the thread (name: role):",
|
|
21529
|
+
"<bots>",
|
|
21530
|
+
bots.map((b, i) => `- ${b.name}: ${clip(b.role, 160)}${i === 0 ? " (the default)" : ""}`).join("\n"),
|
|
21531
|
+
"</bots>",
|
|
21532
|
+
"",
|
|
21533
|
+
"The thread so far (latest last):",
|
|
21534
|
+
"<thread>",
|
|
21535
|
+
tail.map((m) => `${m.author}: ${clip(m.text)}`).join("\n") || "(none)",
|
|
21536
|
+
"</thread>",
|
|
21537
|
+
"",
|
|
21538
|
+
`The message from ${author}:`,
|
|
21539
|
+
"<message>",
|
|
21540
|
+
clip(text, 6e3),
|
|
21541
|
+
"</message>"
|
|
21542
|
+
].join("\n");
|
|
21543
|
+
const questions = [{ type: "choice", name: "who", instructions: `Who is this message for? ${author} writes names loosely: a misspelled or shortened name ("@arc" for archer), a name without @ ("archer, wdyt"), or a reply to what the bot who spoke last just said. Pick the one bot ${author} is addressing or answering. If it is for nobody in particular (a general question, "sitrep", a request for the status), pick ${bots[0].name}, the default.`, choices: bots.map((b) => ({ value: b.name, description: clip(b.role, 160) })) }];
|
|
21544
|
+
return { input: input2, questions };
|
|
21521
21545
|
}
|
|
21522
|
-
function
|
|
21523
|
-
|
|
21546
|
+
function routePrompt(request) {
|
|
21547
|
+
if (isPick(request.bots)) return pickPrompt(request);
|
|
21548
|
+
const { author, text, tail, bots, asked } = request;
|
|
21549
|
+
const roster = bots.map((b) => `- ${b.name}: ${clip(b.role, 160)}${b.busy ? " [busy: working on another turn in this thread]" : ""}${b.relation === "named" ? " [@-mentioned in the message]" : b.relation === "all" ? " [addressed by @all]" : ""}`).join("\n");
|
|
21550
|
+
const input2 = [
|
|
21551
|
+
`A message was just posted in a team chat thread of bots and people by ${author}. For each bot below, decide whether the message needs that bot to act. A bot that acts wakes up and spends a full turn; one that isn't needed stays asleep.`,
|
|
21552
|
+
"",
|
|
21553
|
+
"Bots that could wake (name: role):",
|
|
21554
|
+
"<bots>",
|
|
21555
|
+
roster,
|
|
21556
|
+
"</bots>",
|
|
21557
|
+
"",
|
|
21558
|
+
"The thread so far (latest last):",
|
|
21559
|
+
"<thread>",
|
|
21560
|
+
tail.map((m) => `${m.author}: ${clip(m.text)}`).join("\n") || "(none)",
|
|
21561
|
+
"</thread>",
|
|
21562
|
+
...asked?.length ? ["", `${author} wrote the message below as the end of a turn that answered these messages:`, "<answered>", asked.map((m) => `${m.author}: ${clip(m.text)}`).join("\n"), "</answered>"] : [],
|
|
21563
|
+
"",
|
|
21564
|
+
`The message from ${author}:`,
|
|
21565
|
+
"<message>",
|
|
21566
|
+
clip(text, 6e3),
|
|
21567
|
+
"</message>"
|
|
21568
|
+
].join("\n");
|
|
21569
|
+
const questions = bots.flatMap((b) => [
|
|
21570
|
+
{ type: "predicate", name: `wake-${b.name}`, instructions: WAKE_QUESTION[b.relation](b.name) },
|
|
21571
|
+
...b.busy ? [{ type: "predicate", name: `steer-${b.name}`, instructions: `${b.name} is in the middle of a turn. Is this message urgent enough that ${b.name} must see it now, mid-turn: it changes or cancels what ${b.name} is doing, or ${b.name} can't finish without it? No if it can wait for the turn to end.` }] : []
|
|
21572
|
+
]);
|
|
21573
|
+
return { input: input2, questions };
|
|
21524
21574
|
}
|
|
21525
|
-
var
|
|
21526
|
-
|
|
21527
|
-
|
|
21575
|
+
var choice = (body, bots) => {
|
|
21576
|
+
const a = body?.answers?.find((x) => x?.name === "who");
|
|
21577
|
+
const bot = bots.find((b) => b.name === a?.choice)?.name, score = a?.probabilities?.find((p) => p?.value === bot)?.probability;
|
|
21578
|
+
return bot && typeof score === "number" ? { bot, score } : null;
|
|
21579
|
+
};
|
|
21580
|
+
var probabilities = (body) => new Map((body?.answers ?? []).flatMap((a) => typeof a?.name === "string" && typeof a.probability === "number" ? [[a.name, a.probability]] : []));
|
|
21581
|
+
function decisionRouter(credential, fetchImpl, config3) {
|
|
21582
|
+
const asked = /* @__PURE__ */ new Map();
|
|
21583
|
+
return (request) => {
|
|
21584
|
+
const key = JSON.stringify([request.messageId, request.bots.map((b) => [b.name, b.relation, b.busy])]);
|
|
21585
|
+
let call = asked.get(key);
|
|
21586
|
+
if (!call) {
|
|
21587
|
+
call = ask(request, credential, fetchImpl, config3);
|
|
21588
|
+
asked.set(key, call);
|
|
21589
|
+
if (asked.size > 200) asked.delete(asked.keys().next().value);
|
|
21590
|
+
}
|
|
21591
|
+
return call;
|
|
21592
|
+
};
|
|
21528
21593
|
}
|
|
21529
|
-
|
|
21530
|
-
|
|
21531
|
-
|
|
21532
|
-
|
|
21533
|
-
|
|
21534
|
-
|
|
21535
|
-
|
|
21536
|
-
|
|
21537
|
-
|
|
21538
|
-
|
|
21539
|
-
|
|
21540
|
-
|
|
21541
|
-
|
|
21542
|
-
|
|
21543
|
-
|
|
21544
|
-
|
|
21545
|
-
|
|
21546
|
-
|
|
21547
|
-
|
|
21548
|
-
|
|
21549
|
-
|
|
21594
|
+
async function ask(request, credential, fetchImpl, config3) {
|
|
21595
|
+
const started = Date.now();
|
|
21596
|
+
const failed = (error63) => ({ wake: (isPick(request.bots) ? request.bots.slice(0, 1) : request.bots).map((b) => ({ bot: b.name, steer: false })), ms: Date.now() - started, error: error63 });
|
|
21597
|
+
if (!config3.enabled || config3.api !== "decisions") return failed("routing needs a decision model");
|
|
21598
|
+
const signal = AbortSignal.timeout(config3.timeoutMs);
|
|
21599
|
+
try {
|
|
21600
|
+
const token = await Promise.race([credential(), new Promise((_, reject) => signal.addEventListener("abort", () => reject(signal.reason), { once: true }))]);
|
|
21601
|
+
const res = await fetchImpl(config3.url, {
|
|
21602
|
+
method: "POST",
|
|
21603
|
+
signal,
|
|
21604
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
21605
|
+
body: JSON.stringify({ model: config3.model, ...routePrompt(request) })
|
|
21606
|
+
});
|
|
21607
|
+
if (!res.ok) return failed(`gateway ${res.status}`);
|
|
21608
|
+
const answer = await res.json();
|
|
21609
|
+
if (isPick(request.bots)) {
|
|
21610
|
+
const picked = choice(answer, request.bots);
|
|
21611
|
+
return picked ? { wake: [{ bot: picked.bot, steer: false }], pick: picked, ms: Date.now() - started } : failed("unreadable decision");
|
|
21612
|
+
}
|
|
21613
|
+
const p = probabilities(answer);
|
|
21614
|
+
if (request.bots.some((b) => !p.has(`wake-${b.name}`))) return failed("unreadable decision");
|
|
21615
|
+
return {
|
|
21616
|
+
wake: request.bots.filter((b) => p.get(`wake-${b.name}`) >= WAKE_AT).map((b) => ({ bot: b.name, steer: b.busy && (p.get(`steer-${b.name}`) ?? 0) >= STEER_AT })),
|
|
21617
|
+
ms: Date.now() - started
|
|
21618
|
+
};
|
|
21619
|
+
} catch (e) {
|
|
21620
|
+
return failed(String(e?.message ?? e));
|
|
21621
|
+
}
|
|
21550
21622
|
}
|
|
21551
|
-
|
|
21552
|
-
|
|
21553
|
-
var emptyBucket = () => ({ steps: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costUsd: null, unpricedSteps: 0 });
|
|
21554
|
-
var TOKENS = ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"];
|
|
21555
|
-
var num = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
21556
|
-
var round = (v) => Math.round(v * 1e9) / 1e9;
|
|
21557
|
-
function addUsage(bucket, usage = {}) {
|
|
21558
|
-
bucket.steps += 1;
|
|
21559
|
-
for (const key of TOKENS) bucket[key] += num(usage?.[key]);
|
|
21560
|
-
const cost = usage?.costUsd;
|
|
21561
|
-
if (typeof cost === "number" && Number.isFinite(cost) && cost >= 0) bucket.costUsd = round((bucket.costUsd ?? 0) + cost);
|
|
21562
|
-
else bucket.unpricedSteps += 1;
|
|
21563
|
-
return bucket;
|
|
21623
|
+
function routeRequest({ messageId: messageId2, author, text, bots, facts }) {
|
|
21624
|
+
return { messageId: messageId2, author, text, tail: facts.tail, bots: bots.map((b) => ({ ...b, role: facts.roles.get(b.name)?.role ?? "", busy: facts.roles.get(b.name)?.busy ?? false })) };
|
|
21564
21625
|
}
|
|
21565
|
-
|
|
21566
|
-
|
|
21567
|
-
|
|
21568
|
-
|
|
21569
|
-
|
|
21570
|
-
const b = addUsage(emptyBucket(), step.usage);
|
|
21571
|
-
return [sessionId2, step.sequence, step.stepIndex, Date.parse(step.at), b.inputTokens, b.outputTokens, b.cacheReadTokens, b.cacheWriteTokens, b.costUsd];
|
|
21626
|
+
function applyRouting({ forced, bots }, routing) {
|
|
21627
|
+
const picked = new Map(routing.wake.map((w) => [w.bot, w]));
|
|
21628
|
+
const wake = bots.filter((b) => forced.has(b.name) || picked.has(b.name)).map((b) => ({ bot: b.name, steer: !forced.has(b.name) && Boolean(picked.get(b.name)?.steer) }));
|
|
21629
|
+
const woken = new Set(wake.map((w) => w.bot));
|
|
21630
|
+
return { wake, skipped: bots.filter((b) => !woken.has(b.name)).map((b) => b.name) };
|
|
21572
21631
|
}
|
|
21573
|
-
|
|
21574
|
-
|
|
21575
|
-
|
|
21632
|
+
function logRouting(request, routed, routing, gatherMs) {
|
|
21633
|
+
console.log(JSON.stringify({ at: "route", message: request.messageId, author: request.author, gatherMs, wake: routed.wake, skipped: routed.skipped, modelMs: routing.ms, ...routing.pick ? { pick: routing.pick } : {}, ...routing.error ? { error: routing.error } : {} }));
|
|
21634
|
+
}
|
|
21635
|
+
var ROUTE_BOTS_COLUMN = `(SELECT json_group_array(json_object('name', b.name, 'role', b.role, 'busy', EXISTS (SELECT 1 FROM turns t WHERE t.thread_id = sess.thread_id AND t.bot = b.name AND t.status IN ${OPEN_TURN_STATUSES}))) FROM bots b, sess) AS route_bots`;
|
|
21576
21636
|
|
|
21577
21637
|
// ../../hosted-hub/src/store/closing-check.ts
|
|
21578
21638
|
var TAIL_COLUMN = `(SELECT json_group_array(json_object('author', author, 'text', text)) FROM (SELECT author, text FROM messages
|
|
21579
21639
|
WHERE thread_id = (SELECT thread_id FROM sess) AND author_kind IN ('human', 'agent') AND status IS NULL ORDER BY created_at DESC LIMIT 6)) AS tail`;
|
|
21580
21640
|
var DROP_BELOW = 0.05;
|
|
21581
|
-
var
|
|
21582
|
-
var
|
|
21641
|
+
var CLIP2 = 600;
|
|
21642
|
+
var clip2 = (t, n = CLIP2) => t.length > n ? `${t.slice(0, n)} \u2026` : t;
|
|
21583
21643
|
function closingPrompt({ bot, text, tail, posted, asked }) {
|
|
21584
|
-
const lines = (items) => items.map((m) => `${m.author ? `${m.author}: ` : "- "}${
|
|
21644
|
+
const lines = (items) => items.map((m) => `${m.author ? `${m.author}: ` : "- "}${clip2(m.text)}`).join("\n") || "(none)";
|
|
21585
21645
|
const input2 = [
|
|
21586
21646
|
`${bot} is a bot in a team chat thread of bots and people. Its turn just ended with the closing text below, which will post to the thread as a message unless it adds nothing.`,
|
|
21587
21647
|
"",
|
|
@@ -21622,14 +21682,14 @@ function decisionJudge(credential, fetchImpl = fetch, config3) {
|
|
|
21622
21682
|
const key = `${request.turnId}:${request.text.length}`;
|
|
21623
21683
|
let call = asked.get(key);
|
|
21624
21684
|
if (!call) {
|
|
21625
|
-
call =
|
|
21685
|
+
call = ask2(request, credential, fetchImpl, config3);
|
|
21626
21686
|
asked.set(key, call);
|
|
21627
21687
|
if (asked.size > 200) asked.delete(asked.keys().next().value);
|
|
21628
21688
|
}
|
|
21629
21689
|
return call;
|
|
21630
21690
|
};
|
|
21631
21691
|
}
|
|
21632
|
-
async function
|
|
21692
|
+
async function ask2(request, credential, fetchImpl, config3) {
|
|
21633
21693
|
const started = Date.now();
|
|
21634
21694
|
const done = (c) => {
|
|
21635
21695
|
const closing = { ...c, ms: Date.now() - started };
|
|
@@ -22274,7 +22334,7 @@ __export(external_exports, {
|
|
|
22274
22334
|
tuple: () => tuple,
|
|
22275
22335
|
uint32: () => uint32,
|
|
22276
22336
|
uint64: () => uint64,
|
|
22277
|
-
ulid: () =>
|
|
22337
|
+
ulid: () => ulid2,
|
|
22278
22338
|
undefined: () => _undefined3,
|
|
22279
22339
|
union: () => union,
|
|
22280
22340
|
unknown: () => unknown,
|
|
@@ -23998,7 +24058,7 @@ __export(regexes_exports, {
|
|
|
23998
24058
|
sha512_hex: () => sha512_hex,
|
|
23999
24059
|
string: () => string,
|
|
24000
24060
|
time: () => time,
|
|
24001
|
-
ulid: () =>
|
|
24061
|
+
ulid: () => ulid,
|
|
24002
24062
|
undefined: () => _undefined,
|
|
24003
24063
|
unicodeEmail: () => unicodeEmail,
|
|
24004
24064
|
uppercase: () => uppercase,
|
|
@@ -24010,7 +24070,7 @@ __export(regexes_exports, {
|
|
|
24010
24070
|
});
|
|
24011
24071
|
var cuid = /^[cC][0-9a-z]{6,}$/;
|
|
24012
24072
|
var cuid2 = /^[0-9a-z]+$/;
|
|
24013
|
-
var
|
|
24073
|
+
var ulid = /^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/;
|
|
24014
24074
|
var xid = /^[0-9a-vA-V]{20}$/;
|
|
24015
24075
|
var ksuid = /^[A-Za-z0-9]{27}$/;
|
|
24016
24076
|
var nanoid = /^[a-zA-Z0-9_-]{21}$/;
|
|
@@ -24949,7 +25009,7 @@ var $ZodCUID2 = /* @__PURE__ */ $constructor("$ZodCUID2", (inst, def) => {
|
|
|
24949
25009
|
$ZodStringFormat.init(inst, def);
|
|
24950
25010
|
});
|
|
24951
25011
|
var $ZodULID = /* @__PURE__ */ $constructor("$ZodULID", (inst, def) => {
|
|
24952
|
-
def.pattern ?? (def.pattern =
|
|
25012
|
+
def.pattern ?? (def.pattern = ulid);
|
|
24953
25013
|
$ZodStringFormat.init(inst, def);
|
|
24954
25014
|
});
|
|
24955
25015
|
var $ZodXID = /* @__PURE__ */ $constructor("$ZodXID", (inst, def) => {
|
|
@@ -39167,7 +39227,7 @@ __export(schemas_exports2, {
|
|
|
39167
39227
|
tuple: () => tuple,
|
|
39168
39228
|
uint32: () => uint32,
|
|
39169
39229
|
uint64: () => uint64,
|
|
39170
|
-
ulid: () =>
|
|
39230
|
+
ulid: () => ulid2,
|
|
39171
39231
|
undefined: () => _undefined3,
|
|
39172
39232
|
union: () => union,
|
|
39173
39233
|
unknown: () => unknown,
|
|
@@ -39701,7 +39761,7 @@ var ZodULID = /* @__PURE__ */ $constructor("ZodULID", (inst, def) => {
|
|
|
39701
39761
|
$ZodULID.init(inst, def);
|
|
39702
39762
|
ZodStringFormat.init(inst, def);
|
|
39703
39763
|
});
|
|
39704
|
-
function
|
|
39764
|
+
function ulid2(params) {
|
|
39705
39765
|
return _ulid(ZodULID, params);
|
|
39706
39766
|
}
|
|
39707
39767
|
var ZodXID = /* @__PURE__ */ $constructor("ZodXID", (inst, def) => {
|
|
@@ -41747,6 +41807,47 @@ function date4(params) {
|
|
|
41747
41807
|
return _coercedDate(ZodDate, params);
|
|
41748
41808
|
}
|
|
41749
41809
|
|
|
41810
|
+
// ../../hosted-hub/src/domain/ids.ts
|
|
41811
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
41812
|
+
var alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
41813
|
+
function base32(value) {
|
|
41814
|
+
let out = "";
|
|
41815
|
+
for (let i = 0; i < 26; i++) {
|
|
41816
|
+
out = alphabet[Number(value & 31n)] + out;
|
|
41817
|
+
value >>= 5n;
|
|
41818
|
+
}
|
|
41819
|
+
return out;
|
|
41820
|
+
}
|
|
41821
|
+
function ulid3(now = Date.now()) {
|
|
41822
|
+
return base32(BigInt(now) << 80n | BigInt(`0x${randomBytes(10).toString("hex")}`));
|
|
41823
|
+
}
|
|
41824
|
+
var eventId = () => `evt_${ulid3()}`;
|
|
41825
|
+
function derivedMessageId(...parts) {
|
|
41826
|
+
return `msg_${base32(BigInt(`0x${createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 33)}`) >> 2n)}`;
|
|
41827
|
+
}
|
|
41828
|
+
var sessionId = () => `wrun_${ulid3()}`;
|
|
41829
|
+
var deliveryId = (message, bot) => `dlv_${message}_${bot}`;
|
|
41830
|
+
var messageOfDelivery = (delivery, bot) => delivery.slice("dlv_".length, -`_${bot}`.length);
|
|
41831
|
+
var turnId = (session, chunkIndex) => `trn_${session}_${chunkIndex}`;
|
|
41832
|
+
|
|
41833
|
+
// ../../hosted-hub/src/store/events.ts
|
|
41834
|
+
function eventStatements(threadId2, event, text, at, id = eventId(), only) {
|
|
41835
|
+
const expr = (v) => typeof v === "string" ? { sql: "?", args: [v] } : "sql" in v && "args" in v ? v : { sql: "?", args: [JSON.stringify(v)] };
|
|
41836
|
+
const [t, e] = [expr(text), expr(event)];
|
|
41837
|
+
return [
|
|
41838
|
+
s(
|
|
41839
|
+
`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"}`,
|
|
41840
|
+
id,
|
|
41841
|
+
threadId2,
|
|
41842
|
+
...t.args,
|
|
41843
|
+
at,
|
|
41844
|
+
...e.args,
|
|
41845
|
+
...only?.args ?? []
|
|
41846
|
+
),
|
|
41847
|
+
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)
|
|
41848
|
+
];
|
|
41849
|
+
}
|
|
41850
|
+
|
|
41750
41851
|
// ../../hosted-hub/src/store/routing.ts
|
|
41751
41852
|
var PARTICIPANTS = (thread, except = "''") => `SELECT b.name AS bot FROM bots b, (SELECT ${thread} AS t, ${except} AS x) p WHERE MAX(
|
|
41752
41853
|
COALESCE((SELECT created_at FROM sessions WHERE thread_id = p.t AND bot = b.name ORDER BY created_at DESC LIMIT 1), -2),
|
|
@@ -41782,7 +41883,7 @@ function routingStatements(messageId2, threadId2, bots, now, { by, joins }, held
|
|
|
41782
41883
|
bot,
|
|
41783
41884
|
messageId2
|
|
41784
41885
|
),
|
|
41785
|
-
...steer ? [s("UPDATE deliveries SET steer = 1 WHERE id = ? AND accepted_at IS NULL", deliveryId(messageId2, bot))] : [],
|
|
41886
|
+
...steer === true || steer && steer.has(bot) ? [s("UPDATE deliveries SET steer = 1 WHERE id = ? AND accepted_at IS NULL", deliveryId(messageId2, bot))] : [],
|
|
41786
41887
|
...held[bot] ? [s("UPDATE deliveries SET held_at = ?, held_scope = ? WHERE id = ? AND accepted_at IS NULL", now, held[bot], deliveryId(messageId2, bot))] : []
|
|
41787
41888
|
])
|
|
41788
41889
|
];
|
|
@@ -41804,13 +41905,6 @@ var HOP_LIMIT = "(SELECT json_extract(metadata, '$.workspace.hopLimit') FROM wor
|
|
|
41804
41905
|
var heldByHopLimit = (m) => `(${m}.author_kind = 'agent' AND ${HOP_LIMIT} IS NOT NULL AND ${m}.hop >= ${HOP_LIMIT})`;
|
|
41805
41906
|
var ToolInfo = external_exports.object({ tools: external_exports.object({ static: external_exports.array(external_exports.object({ name: external_exports.string().min(1) })) }) });
|
|
41806
41907
|
|
|
41807
|
-
// ../../hosted-hub/src/store/mentionable.ts
|
|
41808
|
-
var NOW_MS = `(CAST(strftime('%s', 'now') AS INTEGER) * 1000)`;
|
|
41809
|
-
var inRoster = `NOT (runs_on = 'local' AND EXISTS (SELECT 1 FROM local_runner l, workspace_repo w
|
|
41810
|
-
WHERE l.id = 1 AND w.id = 1 AND l.setup_failed_commit = json_extract(w.metadata, '$.commit') AND l.setup_failed_until > ${NOW_MS}))`;
|
|
41811
|
-
var LEASE_LEFT = `SELECT l.setup_failed_until - ${NOW_MS} AS ms FROM local_runner l, workspace_repo w
|
|
41812
|
-
WHERE l.id = 1 AND w.id = 1 AND l.setup_failed_commit = json_extract(w.metadata, '$.commit') AND l.setup_failed_until > ${NOW_MS}`;
|
|
41813
|
-
|
|
41814
41908
|
// ../../hosted-hub/src/domain/wake.ts
|
|
41815
41909
|
var mentioned = (roster, text) => {
|
|
41816
41910
|
const tokens = new Set(mentionTokens(text));
|
|
@@ -41827,6 +41921,58 @@ function recipients(roster, text, from, threadId2 = "", participants = []) {
|
|
|
41827
41921
|
return named.length ? named : roster.slice(0, 1);
|
|
41828
41922
|
}
|
|
41829
41923
|
|
|
41924
|
+
// ../../hosted-hub/src/store/fold-route.ts
|
|
41925
|
+
var relationsOf = (roster, text, me, bots, replyTo) => {
|
|
41926
|
+
const named = new Set(mentioned(roster, text).filter((b) => b !== me));
|
|
41927
|
+
return bots.map((name) => ({ name, relation: named.has(name) ? "named" : hasAll(text) && !replyTo.has(name) ? "all" : "reply" }));
|
|
41928
|
+
};
|
|
41929
|
+
function routedWake(t, messageId2, text, bots, answers, asks) {
|
|
41930
|
+
const all = bots.map((b) => b.name), none = { to: all, steer: /* @__PURE__ */ new Set(), stmts: [] };
|
|
41931
|
+
if (!t.facts || !bots.length || t.thread.startsWith("dm-")) return none;
|
|
41932
|
+
const answer = answers?.get(messageId2);
|
|
41933
|
+
if (!answer) {
|
|
41934
|
+
asks.push({ messageId: messageId2, request: { ...routeRequest({ messageId: messageId2, author: t.me, text, bots, facts: { ...t.facts, tail: t.tail } }), asked: t.asked.map(({ author, text: text2 }) => ({ author, text: text2 })) } });
|
|
41935
|
+
return none;
|
|
41936
|
+
}
|
|
41937
|
+
const routed = applyRouting({ forced: /* @__PURE__ */ new Set(), bots }, answer);
|
|
41938
|
+
logRouting({ messageId: messageId2, author: t.me }, routed, answer, t.facts.gatherMs);
|
|
41939
|
+
return {
|
|
41940
|
+
to: routed.wake.map((w) => w.bot),
|
|
41941
|
+
steer: new Set(routed.wake.filter((w) => w.steer).map((w) => w.bot)),
|
|
41942
|
+
stmts: routed.skipped.flatMap((bot) => eventStatements(t.thread, { kind: "mention_skip", agent: bot, by: t.me, message: messageId2 }, `${bot} skipped: needs nothing from it`, t.at, derivedMessageId(messageId2, "skip", bot), s("EXISTS (SELECT 1 FROM messages WHERE id = ?)", messageId2)))
|
|
41943
|
+
};
|
|
41944
|
+
}
|
|
41945
|
+
var wakeStatements = (t, messageId2, to, steer) => routingStatements(messageId2, t.thread, [...to], t.at, { by: t.me, joins: true }, {}, steer);
|
|
41946
|
+
|
|
41947
|
+
// ../../hosted-hub/src/store/mentionable.ts
|
|
41948
|
+
var NOW_MS = `(CAST(strftime('%s', 'now') AS INTEGER) * 1000)`;
|
|
41949
|
+
var inRoster = `NOT (runs_on = 'local' AND EXISTS (SELECT 1 FROM local_runner l, workspace_repo w
|
|
41950
|
+
WHERE l.id = 1 AND w.id = 1 AND l.setup_failed_commit = json_extract(w.metadata, '$.commit') AND l.setup_failed_until > ${NOW_MS}))`;
|
|
41951
|
+
var LEASE_LEFT = `SELECT l.setup_failed_until - ${NOW_MS} AS ms FROM local_runner l, workspace_repo w
|
|
41952
|
+
WHERE l.id = 1 AND w.id = 1 AND l.setup_failed_commit = json_extract(w.metadata, '$.commit') AND l.setup_failed_until > ${NOW_MS}`;
|
|
41953
|
+
|
|
41954
|
+
// ../../lib/usage.mjs
|
|
41955
|
+
var emptyBucket = () => ({ steps: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costUsd: null, unpricedSteps: 0 });
|
|
41956
|
+
var TOKENS = ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"];
|
|
41957
|
+
var num = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
41958
|
+
var round = (v) => Math.round(v * 1e9) / 1e9;
|
|
41959
|
+
function addUsage(bucket, usage = {}) {
|
|
41960
|
+
bucket.steps += 1;
|
|
41961
|
+
for (const key of TOKENS) bucket[key] += num(usage?.[key]);
|
|
41962
|
+
const cost = usage?.costUsd;
|
|
41963
|
+
if (typeof cost === "number" && Number.isFinite(cost) && cost >= 0) bucket.costUsd = round((bucket.costUsd ?? 0) + cost);
|
|
41964
|
+
else bucket.unpricedSteps += 1;
|
|
41965
|
+
return bucket;
|
|
41966
|
+
}
|
|
41967
|
+
var HOURS_KEPT_MS = 35 * 24 * 36e5;
|
|
41968
|
+
|
|
41969
|
+
// ../../hosted-hub/src/store/usage.ts
|
|
41970
|
+
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)";
|
|
41971
|
+
function stepValues(sessionId2, step) {
|
|
41972
|
+
const b = addUsage(emptyBucket(), step.usage);
|
|
41973
|
+
return [sessionId2, step.sequence, step.stepIndex, Date.parse(step.at), b.inputTokens, b.outputTokens, b.cacheReadTokens, b.cacheWriteTokens, b.costUsd];
|
|
41974
|
+
}
|
|
41975
|
+
|
|
41830
41976
|
// ../../hosted-hub/src/store/notifications.ts
|
|
41831
41977
|
var NOTIFICATION_READS = s("SELECT person, notification_id FROM notification_reads");
|
|
41832
41978
|
var NOTIFICATIONS = s("SELECT message_id, person FROM notifications");
|
|
@@ -48729,9 +48875,9 @@ var TURN_END_SQL = `SELECT * FROM (WITH sess AS (SELECT * FROM sessions WHERE id
|
|
|
48729
48875
|
AND (EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = dd.id AND td.turn_id = (SELECT id FROM open_turn)) OR NOT EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = dd.id))) AS asked,
|
|
48730
48876
|
(SELECT json_group_array(p.text) FROM messages p WHERE p.turn_id = (SELECT id FROM open_turn)
|
|
48731
48877
|
AND p.author = (SELECT bot FROM sess) AND p.author_kind = 'agent' AND p.status IS NULL) AS posted,
|
|
48732
|
-
(SELECT p.id FROM messages p WHERE p.turn_id = (SELECT id FROM open_turn)
|
|
48878
|
+
(SELECT json_object('id', p.id, 'text', p.text) FROM messages p WHERE p.turn_id = (SELECT id FROM open_turn)
|
|
48733
48879
|
AND p.author = (SELECT bot FROM sess) AND p.author_kind = 'agent' AND p.status IS NULL ORDER BY p.created_at DESC, p.rowid DESC LIMIT 1) AS last_post,
|
|
48734
|
-
${TAIL_COLUMN})`;
|
|
48880
|
+
${TAIL_COLUMN}, ${ROUTE_BOTS_COLUMN})`;
|
|
48735
48881
|
var namedDeliveries = (chunks) => [...new Set(chunks.flatMap((c) => c.events.flatMap((e) => e.meta?.deliveryIds ?? [])))];
|
|
48736
48882
|
var completes = (chunks) => chunks.some((c) => c.events.some((e) => e.type === "turn.completed"));
|
|
48737
48883
|
async function readFoldState(get, sessionId2, chunks) {
|
|
@@ -48749,7 +48895,8 @@ function foldState(row) {
|
|
|
48749
48895
|
asked: JSON.parse(String(row.asked ?? "[]")),
|
|
48750
48896
|
posted: JSON.parse(String(row.posted ?? "[]")),
|
|
48751
48897
|
tail: row.tail == null ? [] : JSON.parse(String(row.tail)).reverse(),
|
|
48752
|
-
|
|
48898
|
+
...row.route_bots == null ? {} : { facts: { roles: new Map(JSON.parse(String(row.route_bots)).map((b) => [b.name, { role: b.role, busy: Number(b.busy) === 1 }])), tail: [] } },
|
|
48899
|
+
lastPost: row.last_post == null ? null : JSON.parse(String(row.last_post)),
|
|
48753
48900
|
notify: row.notify ? JSON.parse(String(row.notify)) : null,
|
|
48754
48901
|
foldedIndex: Number(row.folded_index),
|
|
48755
48902
|
open: row.open_turn ? JSON.parse(String(row.open_turn)) : null
|
|
@@ -48760,13 +48907,13 @@ var REVOKED_TURN = `EXISTS (SELECT 1 FROM turn_deliveries td JOIN deliveries d O
|
|
|
48760
48907
|
AND turns.id IS NOT (SELECT archived_turn FROM thread_meta WHERE thread_id = turns.thread_id)`;
|
|
48761
48908
|
var IF_INSERTED = "WHERE changes() > 0";
|
|
48762
48909
|
var lastActWasComms = (ls) => !!ls && Object.keys(ls.calls).length > 0 && Object.values(ls.calls).every((own3) => own3 === true);
|
|
48763
|
-
function fold(state, chunks, now = Date.now(),
|
|
48910
|
+
function fold(state, chunks, now = Date.now(), answers = {}) {
|
|
48764
48911
|
const { session } = state;
|
|
48765
48912
|
const stmts = [], touched = /* @__PURE__ */ new Map(), named = /* @__PURE__ */ new Set();
|
|
48766
48913
|
const log = (type, data, when = "") => stmts.push(s(`INSERT INTO log (thread_id, type, data, at) SELECT ?, ?, ?, ? ${when}`, session.thread, type, JSON.stringify({ threadId: session.thread, agent: session.bot, ...data }), at));
|
|
48767
48914
|
let open3 = state.open && { ...state.open, trigger: state.open.trigger ?? null, lastStep: state.open.lastStep ?? null, status: state.open.status, endedAt: null, error: null };
|
|
48768
48915
|
const deliver = [];
|
|
48769
|
-
const closings = [];
|
|
48916
|
+
const closings = [], routeAsks = [];
|
|
48770
48917
|
let last = state.foldedIndex;
|
|
48771
48918
|
let at = now;
|
|
48772
48919
|
for (const { index, events } of chunks) {
|
|
@@ -48922,7 +49069,7 @@ function fold(state, chunks, now = Date.now(), verdicts = /* @__PURE__ */ new Ma
|
|
|
48922
49069
|
flush();
|
|
48923
49070
|
let text = lastActWasComms(open3.lastStep) ? "" : open3.text.trim();
|
|
48924
49071
|
if (text && open3.status !== "cancelling") {
|
|
48925
|
-
const drop =
|
|
49072
|
+
const drop = answers.closing?.get(open3.id);
|
|
48926
49073
|
if (drop === void 0) closings.push({ turnId: open3.id, bot: session.bot, text, tail: state.tail ?? [], posted: state.posted ?? [], asked: (state.asked ?? []).map(({ author, text: text2 }) => ({ author, text: text2 })) });
|
|
48927
49074
|
else if (drop) {
|
|
48928
49075
|
stmts.push(...eventStatements(session.thread, { kind: "closing_skip", agent: session.bot, by: session.bot }, `${session.bot} skipped its closing text: nothing new`, at, derivedMessageId(open3.id, "closing-skip"), s("EXISTS (SELECT 1 FROM turns WHERE id = ? AND status NOT IN ('cancelling','cancelled'))", open3.id)));
|
|
@@ -48939,13 +49086,16 @@ function fold(state, chunks, now = Date.now(), verdicts = /* @__PURE__ */ new Ma
|
|
|
48939
49086
|
stmts.push(...notificationFor(messageId2, "agent", text, state.notify ?? void 0));
|
|
48940
49087
|
stmts.push(s(`INSERT OR IGNORE INTO message_attachments (message_id, position, upload_id, name, size)
|
|
48941
49088
|
SELECT ?, position, upload_id, name, size FROM turn_attachments WHERE turn_id = ? AND EXISTS (SELECT 1 FROM messages WHERE id = ?) ORDER BY position`, messageId2, open3.id, messageId2));
|
|
48942
|
-
const
|
|
48943
|
-
const
|
|
48944
|
-
|
|
49089
|
+
const me = session.bot, turn = { me, thread: session.thread, roster: state.roster, facts: state.facts && { ...state.facts, tail: state.tail ?? [] }, tail: state.tail ?? [], asked: state.asked ?? [], at };
|
|
49090
|
+
const onPost = text === "" ? state.lastPost : null, replyTo = new Set((state.asked ?? []).map((a) => a.author));
|
|
49091
|
+
const base = [.../* @__PURE__ */ new Set([...recipients(state.roster, text, { name: me, kind: "agent" }, session.thread, state.participants), ...onPost ? [] : handedBack(state, text, named)])];
|
|
49092
|
+
const first = text ? routedWake(turn, messageId2, text, relationsOf(state.roster, text, me, base, replyTo), answers.routes, routeAsks) : { to: base, steer: /* @__PURE__ */ new Set(), stmts: [] };
|
|
49093
|
+
stmts.push(...first.stmts, ...wakeStatements(turn, messageId2, first.to, first.steer));
|
|
48945
49094
|
deliver.push(messageId2);
|
|
48946
49095
|
if (onPost) {
|
|
48947
|
-
|
|
48948
|
-
|
|
49096
|
+
const back = routedWake(turn, onPost.id, onPost.text, handedBack(state, "", named).map((name) => ({ name, relation: "reply" })), answers.routes, routeAsks);
|
|
49097
|
+
stmts.push(...back.stmts, ...wakeStatements(turn, onPost.id, back.to, back.steer));
|
|
49098
|
+
deliver.push(onPost.id);
|
|
48949
49099
|
}
|
|
48950
49100
|
}
|
|
48951
49101
|
end("done");
|
|
@@ -49000,17 +49150,20 @@ function fold(state, chunks, now = Date.now(), verdicts = /* @__PURE__ */ new Ma
|
|
|
49000
49150
|
stmts.push(s(`INSERT INTO session_folds (session_id, folded_index, folded_at) VALUES (?, ?, ?)
|
|
49001
49151
|
ON CONFLICT (session_id) DO UPDATE SET folded_index = excluded.folded_index, folded_at = excluded.folded_at`, session.id, last, now));
|
|
49002
49152
|
}
|
|
49003
|
-
return { statements: stmts, deliver, ...closings.length ? { closings } : {} };
|
|
49153
|
+
return { statements: stmts, deliver, ...closings.length ? { closings } : {}, ...routeAsks.length ? { routes: routeAsks } : {} };
|
|
49004
49154
|
}
|
|
49005
|
-
async function foldJudged(state, chunks, now,
|
|
49155
|
+
async function foldJudged(state, chunks, now, judges2 = {}) {
|
|
49006
49156
|
const first = fold(structuredClone(state), chunks, now);
|
|
49007
|
-
|
|
49008
|
-
|
|
49009
|
-
|
|
49010
|
-
|
|
49011
|
-
|
|
49157
|
+
const [closing, routes] = await Promise.all([
|
|
49158
|
+
Promise.all((judges2.closing ? first.closings ?? [] : []).map(async (c) => [c.turnId, (await judges2.closing(c)).drop])),
|
|
49159
|
+
Promise.all((judges2.router ? first.routes ?? [] : []).map(async (r) => [r.messageId, await judges2.router(r.request)]))
|
|
49160
|
+
]);
|
|
49161
|
+
if (!closing.length && !routes.length) return first;
|
|
49162
|
+
return fold(state, chunks, now, { closing: new Map(closing), routes: new Map(routes) });
|
|
49163
|
+
}
|
|
49164
|
+
async function foldInTx(tx, sessionId2, chunks, judges2) {
|
|
49012
49165
|
const state = await readFoldState((sql, ...args) => tx.get(sql, ...args), sessionId2, chunks);
|
|
49013
|
-
const result = state ? await foldJudged(state, chunks, Date.now(),
|
|
49166
|
+
const result = state ? await foldJudged(state, chunks, Date.now(), judges2) : foldSubagent(subagentState(sessionId2, await tx.get(SUBAGENT_STATE_SQL, sessionId2)), chunks);
|
|
49014
49167
|
for (const stmt of result.statements) await tx.write(stmt.sql, ...stmt.args);
|
|
49015
49168
|
return result.deliver;
|
|
49016
49169
|
}
|
|
@@ -49048,11 +49201,11 @@ function foldSubagent(state, chunks, now = Date.now()) {
|
|
|
49048
49201
|
}
|
|
49049
49202
|
|
|
49050
49203
|
// ../../hosted-hub/bots/world.ts
|
|
49051
|
-
var
|
|
49204
|
+
var judges = { closing: decisionJudge(import_oidc.getVercelOidcToken, fetch, GATE), router: decisionRouter(import_oidc.getVercelOidcToken, fetch, GATE) };
|
|
49052
49205
|
async function foldAppend(tx, { runId, name, chunks, firstIndex }) {
|
|
49053
49206
|
if (name !== sessionCodec.streamName(runId)) return;
|
|
49054
49207
|
const decoded = await Promise.all(chunks.map(async (c, k2) => ({ index: firstIndex + k2, events: await sessionCodec.decode(c) })));
|
|
49055
|
-
return foldInTx(tx, runId, decoded,
|
|
49208
|
+
return foldInTx(tx, runId, decoded, judges);
|
|
49056
49209
|
}
|
|
49057
49210
|
async function createWorld() {
|
|
49058
49211
|
const url2 = process.env.TURSO_DATABASE_URL;
|
package/hooks/mention-gate.d.ts
DELETED
package/hooks/mention-gate.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { getVercelOidcToken } from "@vercel/oidc";
|
|
2
|
-
import { defineHook } from "eve/hooks";
|
|
3
|
-
import { registryBase, registryFetch } from "../registry-fetch.mjs";
|
|
4
|
-
import { credentialCache, hubCredentialLoader, screenInbound } from "../mention-gate.mjs";
|
|
5
|
-
// The mention gate, on the bot's side: before the main model runs a turn an
|
|
6
|
-
// @-mention or a DM woke, a cheap model decides from this bot's instructions
|
|
7
|
-
// and the delivery whether a reply is needed. On a skip the hub posts
|
|
8
|
-
// "<bot> skipped: <reason>" and the turn is cancelled before any model call.
|
|
9
|
-
// Any failure answers. The hub names the bot from its session and serves its
|
|
10
|
-
// instructions, so a deployed bot and a Mac bot run the same code. Its AI Gateway
|
|
11
|
-
// credential is an OIDC token for the workspace's bots project: a deployed bot
|
|
12
|
-
// carries its own, and a Mac bot has none, so it asks the hub for one.
|
|
13
|
-
// See packages/agent-kit/mention-gate.mjs.
|
|
14
|
-
const deploymentToken = async () => ({ ok: true, token: await getVercelOidcToken() });
|
|
15
|
-
const credential = credentialCache(process.env.VERCEL ? deploymentToken : hubCredentialLoader(registryFetch));
|
|
16
|
-
export default defineHook({
|
|
17
|
-
events: {
|
|
18
|
-
async "message.received"(event, ctx) {
|
|
19
|
-
if (ctx.session.parent || !(process.env.VERCEL || process.env.HUB_TOKEN))
|
|
20
|
-
return; // subagents are never gated; nor is a bot with no hosted hub
|
|
21
|
-
const { cancel } = await screenInbound({
|
|
22
|
-
text: event.data.message,
|
|
23
|
-
sessionId: ctx.session.id,
|
|
24
|
-
registryUrl: registryBase(),
|
|
25
|
-
credential,
|
|
26
|
-
});
|
|
27
|
-
if (cancel)
|
|
28
|
-
ctx.cancel();
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
});
|