eve-hub 0.6.51 → 0.6.52
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/hooks/mention-gate.js +8 -14
- package/mention-gate.d.mts +6 -0
- package/mention-gate.mjs +22 -35
- package/package.json +1 -1
- package/stubs.mjs +2 -2
- package/world.mjs +65 -39
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# eve-hub
|
|
2
2
|
|
|
3
|
+
## 0.6.52
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- The mention gate runs on deployed bots, not only Mac bots: the hook ships in the deployed kit, the hub names the bot from its session and sends its role, and the model credential comes from the hub. It reads the message id of a hosted hub's delivery, so a mention that asks nothing of the bot is skipped.
|
|
8
|
+
|
|
3
9
|
## 0.6.51
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/hooks/mention-gate.js
CHANGED
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
import { defineHook } from "eve/hooks";
|
|
2
|
-
import { registryBase } from "../registry-fetch.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { botInstructions, credentialCache, gatewayCredentialLoader, screenInbound } from "../mention-gate.mjs";
|
|
2
|
+
import { registryBase, registryFetch } from "../registry-fetch.mjs";
|
|
3
|
+
import { credentialCache, hubCredentialLoader, screenInbound } from "../mention-gate.mjs";
|
|
5
4
|
// The mention gate, on the bot's side: before the main model runs a turn an
|
|
6
5
|
// @-mention or a DM woke, a cheap model decides from this bot's instructions
|
|
7
6
|
// and the delivery whether a reply is needed. On a skip the hub posts
|
|
8
7
|
// "<bot> skipped: <reason>" and the turn is cancelled before any model call.
|
|
9
|
-
// Any failure answers.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
});
|
|
8
|
+
// Any failure answers. The hub names the bot from its session and serves its
|
|
9
|
+
// instructions and the model credential, so a deployed bot and a Mac bot run
|
|
10
|
+
// the same code. See packages/agent-kit/mention-gate.mjs.
|
|
11
|
+
const credential = credentialCache(hubCredentialLoader(registryFetch));
|
|
14
12
|
export default defineHook({
|
|
15
13
|
events: {
|
|
16
14
|
async "message.received"(event, ctx) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return; // subagents are never gated
|
|
15
|
+
if (ctx.session.parent || !process.env.HUB_TOKEN)
|
|
16
|
+
return; // subagents are never gated; nor is a bot with no hosted hub
|
|
20
17
|
const { cancel } = await screenInbound({
|
|
21
18
|
text: event.data.message,
|
|
22
19
|
sessionId: ctx.session.id,
|
|
23
|
-
turnId: event.data.turnId,
|
|
24
|
-
agent,
|
|
25
20
|
registryUrl: registryBase(),
|
|
26
|
-
instructions: botInstructions(agent),
|
|
27
21
|
credential,
|
|
28
22
|
});
|
|
29
23
|
if (cancel)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type GateConfig = { enabled: boolean; model: string; api: 'chat' | 'decisions'; reasoningEffort: string; timeoutMs: number; threshold: number; incidentalThreshold: number; url: string };
|
|
2
|
+
export function gateConfig(env?: Record<string, string | undefined>, file?: unknown): GateConfig;
|
|
3
|
+
export function opensWithHandle(text: string, agent: string): boolean;
|
|
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
|
+
export function hubCredentialLoader(fetchHub: (path: string, init: { headers?: Record<string, string> }) => Promise<Response>): (opts?: { refresh?: boolean }) => Promise<{ ok: boolean; token?: string; teamId?: string | null; error?: string }>;
|
package/mention-gate.mjs
CHANGED
|
@@ -22,9 +22,7 @@
|
|
|
22
22
|
// HUB_MENTION_GATE_URL (tests).
|
|
23
23
|
import { codeRanges } from "./mention-tokens.mjs";
|
|
24
24
|
import { registryHeaders } from "./registry-fetch.mjs";
|
|
25
|
-
import {
|
|
26
|
-
import { join } from "node:path";
|
|
27
|
-
import { pathToFileURL } from "node:url";
|
|
25
|
+
import { createHash } from "node:crypto";
|
|
28
26
|
|
|
29
27
|
export { codeRanges };
|
|
30
28
|
|
|
@@ -122,8 +120,10 @@ const clip = (text, n) => {
|
|
|
122
120
|
};
|
|
123
121
|
|
|
124
122
|
// The id of the message marked → in a delivery (registry envelope()).
|
|
123
|
+
// A hosted hub's delivery is one message: `[thread <id> "<title>"] [<message id>] <author>: …`.
|
|
125
124
|
export function triggerId(envelope) {
|
|
126
|
-
|
|
125
|
+
const text = String(envelope ?? "");
|
|
126
|
+
return text.match(/^→ \[([^\]\s]+)\]/m)?.[1] ?? text.match(/^\[thread \S+ "(?:[^"\\]|\\.)*"\] \[([^\]\s]+)\] /m)?.[1] ?? null;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// The delivery's closing address line ("[you were addressed by @x. … @x
|
|
@@ -380,7 +380,7 @@ export function credentialCache(load, { ttlMs = 10 * 60e3, failMs = 60e3, now =
|
|
|
380
380
|
const get = async ({ refresh = false } = {}) => {
|
|
381
381
|
if (cached && !refresh && now() < cached.until) return cached.value;
|
|
382
382
|
inflight ??= Promise.resolve()
|
|
383
|
-
.then(load)
|
|
383
|
+
.then(() => load({ refresh }))
|
|
384
384
|
.catch((error) => ({ ok: false, error: String(error?.message ?? error) }))
|
|
385
385
|
.then((r) => {
|
|
386
386
|
const value = r?.ok && r.token ? { token: r.token, teamId: r.teamId ?? null } : null;
|
|
@@ -410,20 +410,23 @@ async function hubCall(fetchImpl, url, body) {
|
|
|
410
410
|
// end cancelled). Anything that fails on the way answers.
|
|
411
411
|
// text: the delivery (message.received's data.message)
|
|
412
412
|
// credential: ({ refresh }) → { token, teamId? } | null (credentialCache)
|
|
413
|
-
|
|
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 }) {
|
|
414
415
|
const messageId = triggerId(text);
|
|
415
|
-
if (!
|
|
416
|
+
if (!registryUrl || !messageId) return { cancel: false };
|
|
416
417
|
let check;
|
|
417
418
|
try {
|
|
418
|
-
check = await hubCall(fetchImpl, `${registryUrl}/api/mention-gate/check`, { agent, messageId, sessionId });
|
|
419
|
+
check = await hubCall(fetchImpl, `${registryUrl}/api/mention-gate/check`, { ...(agent ? { agent } : {}), messageId, sessionId });
|
|
419
420
|
} catch (error) {
|
|
420
421
|
log?.(`[mention-gate] check for ${messageId} failed, answering: ${error?.message ?? error}`);
|
|
421
422
|
return { cancel: false };
|
|
422
423
|
}
|
|
423
424
|
if (!check?.gate || !check.config) return { cancel: false };
|
|
425
|
+
agent ??= check.agent;
|
|
426
|
+
instructions ||= check.instructions ?? "";
|
|
424
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 });
|
|
425
428
|
try {
|
|
426
|
-
const ack = await hubCall(fetchImpl, `${registryUrl}/api/mention-gate/report`, { agent, messageId, sessionId,
|
|
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 });
|
|
427
430
|
return { cancel: Boolean(r.skip && ack?.skip), result: r };
|
|
428
431
|
} catch (error) {
|
|
429
432
|
log?.(`[mention-gate] report for ${messageId} failed, answering: ${error?.message ?? error}`);
|
|
@@ -431,31 +434,15 @@ export async function screenInbound({ text, sessionId, turnId, agent, registryUr
|
|
|
431
434
|
}
|
|
432
435
|
}
|
|
433
436
|
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const data = JSON.parse(readFileSync(join(workspace, "hub.workspace.json"), "utf8"));
|
|
445
|
-
const role = data?.agents?.find?.((a) => a.name === agent)?.role;
|
|
446
|
-
if (role) text = `You are **${agent}**. ${role}`;
|
|
447
|
-
}
|
|
448
|
-
} catch {}
|
|
449
|
-
instructionsCache.set(agent, text);
|
|
450
|
-
return text;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// The AI Gateway credential the bot's own model calls use: eve's resolver
|
|
454
|
-
// (the bot process already loaded the workspace's env files), cached.
|
|
455
|
-
export function gatewayCredentialLoader(resolverFile) {
|
|
456
|
-
return async () => {
|
|
457
|
-
const mod = await import(pathToFileURL(resolverFile).href);
|
|
458
|
-
const c = await mod.resolveGatewayModelCredential();
|
|
459
|
-
return { ok: true, token: c.token, teamId: c.teamId ?? null };
|
|
437
|
+
// A deployed bot's AI Gateway credential: the hub's own (the workspace owner's sign-in, kept fresh there), asked for with the bot's HUB_TOKEN.
|
|
438
|
+
// `fetchHub(path, init)` is registryFetch. On a refresh it names the token AI Gateway refused, as eve's served credential does.
|
|
439
|
+
export function hubCredentialLoader(fetchHub) {
|
|
440
|
+
let last = null;
|
|
441
|
+
return async ({ refresh = false } = {}) => {
|
|
442
|
+
const res = await fetchHub("/api/model-credential", { headers: refresh && last ? { "x-eve-model-rejected-token-sha256": createHash("sha256").update(last).digest("hex") } : {} });
|
|
443
|
+
if (!res.ok) return { ok: false, error: `model credential ${res.status}` };
|
|
444
|
+
const c = await res.json();
|
|
445
|
+
last = c.token ?? null;
|
|
446
|
+
return { ok: Boolean(c.token), token: c.token, teamId: c.teamId ?? null };
|
|
460
447
|
};
|
|
461
448
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eve-hub",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.52",
|
|
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
|
@@ -50,12 +50,12 @@ export const MAIN_KIT_FILES = {
|
|
|
50
50
|
// sandbox that resolves /workspace as itself, which a Vercel Sandbox does),
|
|
51
51
|
// the kit's bash (HostSandbox's root, the daemon's env; eve's own bash runs
|
|
52
52
|
// instead), the browser and the screenshot paths of attach_image/view_image,
|
|
53
|
-
// the
|
|
54
|
-
// daemon's per-process HUB_AGENT and patched model credential), restart_hub (the hub's own code), memory (the workspace's files), and main's prune schedule (it cleans the hub machine's disk).
|
|
53
|
+
// restart_hub (the hub's own code), memory (the workspace's files), and main's prune schedule (it cleans the hub machine's disk).
|
|
55
54
|
// main's compact and dash schedules stay: off the hub's machine compact finds no session to compact
|
|
56
55
|
// (hosted sessions carry no last-model-call time), and dash has no PR data (its lookup runs `gh`).
|
|
57
56
|
export const DEPLOYED_KIT_FILES = new Set([
|
|
58
57
|
"channels/eve.ts",
|
|
58
|
+
"hooks/mention-gate.ts",
|
|
59
59
|
"schedules/compact.ts",
|
|
60
60
|
"schedules/dash.ts",
|
|
61
61
|
"skills/onboarding-profile.ts",
|
package/world.mjs
CHANGED
|
@@ -20242,42 +20242,6 @@ var LEASE_LEFT = `SELECT l.setup_failed_until - ${NOW_MS} AS ms FROM local_runne
|
|
|
20242
20242
|
// ../../hosted-hub/src/store/schema.ts
|
|
20243
20243
|
var OPEN_TURN_STATUSES = "('streaming','cancelling')";
|
|
20244
20244
|
|
|
20245
|
-
// ../../hosted-hub/src/domain/wake.ts
|
|
20246
|
-
var escape = (s2) => s2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20247
|
-
var mentioned = (roster, text) => roster.filter((bot) => new RegExp(`(^|[^\\w@])@${escape(bot)}(?![\\w-])`, "i").test(text));
|
|
20248
|
-
var hasAll = (text) => /(^|[^\w@])@all(?![\w-])/i.test(text);
|
|
20249
|
-
function recipients(roster, text, from, threadId2 = "", participants = []) {
|
|
20250
|
-
if (threadId2.startsWith("dm-")) {
|
|
20251
|
-
const bot = threadId2.slice(3);
|
|
20252
|
-
return bot !== from.name && roster.includes(bot) ? [bot] : [];
|
|
20253
|
-
}
|
|
20254
|
-
const named = [.../* @__PURE__ */ new Set([...mentioned(roster, text), ...hasAll(text) ? participants.filter((b) => roster.includes(b)) : []])].filter((bot) => bot !== from.name);
|
|
20255
|
-
if (from.kind === "agent") return named;
|
|
20256
|
-
return named.length ? named : roster.slice(0, 1);
|
|
20257
|
-
}
|
|
20258
|
-
|
|
20259
|
-
// ../../lib/usage.mjs
|
|
20260
|
-
var emptyBucket = () => ({ steps: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costUsd: null, unpricedSteps: 0 });
|
|
20261
|
-
var TOKENS = ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"];
|
|
20262
|
-
var num = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
20263
|
-
var round = (v) => Math.round(v * 1e9) / 1e9;
|
|
20264
|
-
function addUsage(bucket, usage = {}) {
|
|
20265
|
-
bucket.steps += 1;
|
|
20266
|
-
for (const key of TOKENS) bucket[key] += num(usage?.[key]);
|
|
20267
|
-
const cost = usage?.costUsd;
|
|
20268
|
-
if (typeof cost === "number" && Number.isFinite(cost) && cost >= 0) bucket.costUsd = round((bucket.costUsd ?? 0) + cost);
|
|
20269
|
-
else bucket.unpricedSteps += 1;
|
|
20270
|
-
return bucket;
|
|
20271
|
-
}
|
|
20272
|
-
var HOURS_KEPT_MS = 35 * 24 * 36e5;
|
|
20273
|
-
|
|
20274
|
-
// ../../hosted-hub/src/store/usage.ts
|
|
20275
|
-
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)";
|
|
20276
|
-
function stepValues(sessionId2, step) {
|
|
20277
|
-
const b = addUsage(emptyBucket(), step.usage);
|
|
20278
|
-
return [sessionId2, step.sequence, step.stepIndex, Date.parse(step.at), b.inputTokens, b.outputTokens, b.cacheReadTokens, b.cacheWriteTokens, b.costUsd];
|
|
20279
|
-
}
|
|
20280
|
-
|
|
20281
20245
|
// mention-tokens.mjs
|
|
20282
20246
|
function codeRanges(text) {
|
|
20283
20247
|
const s2 = String(text ?? "");
|
|
@@ -20339,6 +20303,44 @@ function mentionTokens(text) {
|
|
|
20339
20303
|
return out;
|
|
20340
20304
|
}
|
|
20341
20305
|
|
|
20306
|
+
// ../../hosted-hub/src/domain/wake.ts
|
|
20307
|
+
var mentioned = (roster, text) => {
|
|
20308
|
+
const tokens = new Set(mentionTokens(text));
|
|
20309
|
+
return roster.filter((bot) => tokens.has(bot.toLowerCase()));
|
|
20310
|
+
};
|
|
20311
|
+
var hasAll = (text) => mentionTokens(text).includes("all");
|
|
20312
|
+
function recipients(roster, text, from, threadId2 = "", participants = []) {
|
|
20313
|
+
if (threadId2.startsWith("dm-")) {
|
|
20314
|
+
const bot = threadId2.slice(3);
|
|
20315
|
+
return bot !== from.name && roster.includes(bot) ? [bot] : [];
|
|
20316
|
+
}
|
|
20317
|
+
const named = [.../* @__PURE__ */ new Set([...mentioned(roster, text), ...hasAll(text) ? participants.filter((b) => roster.includes(b)) : []])].filter((bot) => bot !== from.name);
|
|
20318
|
+
if (from.kind === "agent") return named;
|
|
20319
|
+
return named.length ? named : roster.slice(0, 1);
|
|
20320
|
+
}
|
|
20321
|
+
|
|
20322
|
+
// ../../lib/usage.mjs
|
|
20323
|
+
var emptyBucket = () => ({ steps: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costUsd: null, unpricedSteps: 0 });
|
|
20324
|
+
var TOKENS = ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"];
|
|
20325
|
+
var num = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
20326
|
+
var round = (v) => Math.round(v * 1e9) / 1e9;
|
|
20327
|
+
function addUsage(bucket, usage = {}) {
|
|
20328
|
+
bucket.steps += 1;
|
|
20329
|
+
for (const key of TOKENS) bucket[key] += num(usage?.[key]);
|
|
20330
|
+
const cost = usage?.costUsd;
|
|
20331
|
+
if (typeof cost === "number" && Number.isFinite(cost) && cost >= 0) bucket.costUsd = round((bucket.costUsd ?? 0) + cost);
|
|
20332
|
+
else bucket.unpricedSteps += 1;
|
|
20333
|
+
return bucket;
|
|
20334
|
+
}
|
|
20335
|
+
var HOURS_KEPT_MS = 35 * 24 * 36e5;
|
|
20336
|
+
|
|
20337
|
+
// ../../hosted-hub/src/store/usage.ts
|
|
20338
|
+
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)";
|
|
20339
|
+
function stepValues(sessionId2, step) {
|
|
20340
|
+
const b = addUsage(emptyBucket(), step.usage);
|
|
20341
|
+
return [sessionId2, step.sequence, step.stepIndex, Date.parse(step.at), b.inputTokens, b.outputTokens, b.cacheReadTokens, b.cacheWriteTokens, b.costUsd];
|
|
20342
|
+
}
|
|
20343
|
+
|
|
20342
20344
|
// ../../hosted-hub/src/store/notifications.ts
|
|
20343
20345
|
var NOTIFICATION_READS = s("SELECT person, notification_id FROM notification_reads");
|
|
20344
20346
|
var NOTIFICATIONS = s("SELECT message_id, person FROM notifications");
|
|
@@ -27226,7 +27228,8 @@ var TASK_CARD = `(SELECT CASE WHEN json_array_length(json_extract(t.activity, '$
|
|
|
27226
27228
|
AND m.rowid = (SELECT MAX(x.rowid) FROM messages x WHERE x.turn_id = m.turn_id))`;
|
|
27227
27229
|
var FACT_COLUMNS = `status, status_from, metadata, verdict, decisions, tested, ${TASK_CARD} AS task_card, event`;
|
|
27228
27230
|
var LINKED = "EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = d.id)";
|
|
27229
|
-
var DELIVERY_STATUS = `CASE WHEN d.
|
|
27231
|
+
var DELIVERY_STATUS = `CASE WHEN json_extract(d.gate, '$.decision') = 'skip' THEN 'skipped: ' || json_extract(d.gate, '$.reason')
|
|
27232
|
+
WHEN d.accepted_at IS NOT NULL OR ${LINKED} THEN COALESCE((SELECT 'waiting: on your answer to its ' || r.kind || ' request'
|
|
27230
27233
|
FROM input_requests r JOIN sessions rs ON rs.id = r.session_id
|
|
27231
27234
|
WHERE r.session_id = d.session_id AND r.status = 'pending' AND rs.ended_at IS NULL
|
|
27232
27235
|
AND r.created_at <= COALESCE((SELECT t.started_at FROM turn_deliveries td JOIN turns t ON t.id = td.turn_id WHERE td.delivery_id = d.id), d.accepted_at) LIMIT 1),
|
|
@@ -27245,9 +27248,22 @@ var RESTARTED_TURNS_READ = s(`SELECT t.thread_id, t.bot, r.claimed_at FROM turns
|
|
|
27245
27248
|
AND NOT EXISTS (SELECT 1 FROM session_folds f WHERE f.session_id = t.session_id AND f.folded_at >= r.claimed_at)`);
|
|
27246
27249
|
|
|
27247
27250
|
// ../../hosted-hub/src/store/fold.ts
|
|
27251
|
+
function handedBack({ session, roster, asked = [], posted = [], notify }, text, named) {
|
|
27252
|
+
if (session.thread.startsWith("dm-")) return [];
|
|
27253
|
+
const known = new Set([...roster, ...notify ? [notify.person, ...notify.names] : [], "all"].map((n) => n.toLowerCase())), me = session.bot.toLowerCase();
|
|
27254
|
+
if ([text, ...posted].some((t) => mentionTokens(t).some((name) => name !== me && known.has(name)))) return [];
|
|
27255
|
+
return [...new Set(asked.filter((a) => (a.linked || named.has(a.id)) && a.author !== session.bot && roster.includes(a.author) && mentionTokens(a.text).includes(me)).map((a) => a.author))];
|
|
27256
|
+
}
|
|
27248
27257
|
var FOLD_STATE_SQL = `SELECT s.id, s.thread_id, s.bot, COALESCE(f.folded_index, -1) AS folded_index, (SELECT json_group_array(name) FROM (SELECT name FROM bots WHERE ${inRoster} ORDER BY position)) AS roster,
|
|
27249
27258
|
(SELECT json_group_array(bot) FROM (${PARTICIPANTS("s.thread_id")})) AS participants,
|
|
27250
27259
|
(SELECT d.message_id FROM deliveries d WHERE d.bot = s.bot AND d.held_at IS NOT NULL AND d.held_paused IS NOT 1 AND d.accepted_at IS NULL AND d.failed_at IS NULL AND d.revoked_at IS NULL ORDER BY d.rowid LIMIT 1) AS held,
|
|
27260
|
+
(SELECT json_group_array(json_object('id', dd.id, 'author', m.author, 'text', m.text, 'linked', EXISTS (SELECT 1 FROM turn_deliveries td JOIN turns t ON t.id = td.turn_id WHERE td.delivery_id = dd.id AND t.status IN ${OPEN_TURN_STATUSES})))
|
|
27261
|
+
FROM deliveries dd JOIN messages m ON m.id = dd.message_id WHERE dd.session_id = s.id AND m.author_kind = 'agent'
|
|
27262
|
+
AND (EXISTS (SELECT 1 FROM turn_deliveries td JOIN turns t ON t.id = td.turn_id WHERE td.delivery_id = dd.id AND t.status IN ${OPEN_TURN_STATUSES}) OR NOT EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = dd.id))) AS asked,
|
|
27263
|
+
(SELECT json_group_array(p.text) FROM messages p WHERE p.turn_id = (SELECT t.id FROM turns t WHERE t.session_id = s.id AND t.status IN ${OPEN_TURN_STATUSES} ORDER BY t.started_at DESC LIMIT 1)
|
|
27264
|
+
AND p.author = s.bot AND p.author_kind = 'agent' AND p.status IS NULL) AS posted,
|
|
27265
|
+
(SELECT p.id FROM messages p WHERE p.turn_id = (SELECT t.id FROM turns t WHERE t.session_id = s.id AND t.status IN ${OPEN_TURN_STATUSES} ORDER BY t.started_at DESC LIMIT 1)
|
|
27266
|
+
AND p.author = s.bot AND p.author_kind = 'agent' AND p.status IS NULL ORDER BY p.created_at DESC, p.rowid DESC LIMIT 1) AS last_post,
|
|
27251
27267
|
(SELECT json_object('person', person, 'names', json(names)) FROM human_notify) AS notify,
|
|
27252
27268
|
(SELECT json_object('status', t.status, 'id', t.id, 'text', t.reply_text, 'lastStep', json(t.last_step), 'activity', json(t.activity),
|
|
27253
27269
|
'trigger', ${triggerOf("t")}) FROM turns t WHERE t.session_id = s.id AND t.status IN ${OPEN_TURN_STATUSES}
|
|
@@ -27262,6 +27278,9 @@ function foldState(row) {
|
|
|
27262
27278
|
roster: JSON.parse(String(row.roster)),
|
|
27263
27279
|
participants: JSON.parse(String(row.participants)),
|
|
27264
27280
|
held: row.held == null ? null : String(row.held),
|
|
27281
|
+
asked: JSON.parse(String(row.asked ?? "[]")),
|
|
27282
|
+
posted: JSON.parse(String(row.posted ?? "[]")),
|
|
27283
|
+
lastPost: row.last_post == null ? null : String(row.last_post),
|
|
27265
27284
|
notify: row.notify ? JSON.parse(String(row.notify)) : null,
|
|
27266
27285
|
foldedIndex: Number(row.folded_index),
|
|
27267
27286
|
open: row.open_turn ? JSON.parse(String(row.open_turn)) : null,
|
|
@@ -27275,7 +27294,7 @@ var IF_INSERTED = "WHERE changes() > 0";
|
|
|
27275
27294
|
var lastActWasOwnPost = (ls) => !!ls && Object.keys(ls.calls).length > 0 && Object.values(ls.calls).every((own3) => own3 === true);
|
|
27276
27295
|
function fold(state, chunks, now = Date.now()) {
|
|
27277
27296
|
const { session } = state;
|
|
27278
|
-
const stmts = [], touched = /* @__PURE__ */ new Map();
|
|
27297
|
+
const stmts = [], touched = /* @__PURE__ */ new Map(), named = /* @__PURE__ */ new Set();
|
|
27279
27298
|
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));
|
|
27280
27299
|
let open3 = state.open && { ...state.open, trigger: state.open.trigger ?? null, lastStep: state.open.lastStep ?? null, status: state.open.status, endedAt: null, error: null };
|
|
27281
27300
|
const deliver = [];
|
|
@@ -27363,7 +27382,9 @@ function fold(state, chunks, now = Date.now()) {
|
|
|
27363
27382
|
data.message.length - state2
|
|
27364
27383
|
));
|
|
27365
27384
|
}
|
|
27385
|
+
if (event.type === "turn.started") named.clear();
|
|
27366
27386
|
for (const deliveryId2 of event.meta?.deliveryIds ?? []) {
|
|
27387
|
+
named.add(deliveryId2);
|
|
27367
27388
|
open3.trigger ??= messageOfDelivery(deliveryId2, session.bot);
|
|
27368
27389
|
stmts.push(s("INSERT OR IGNORE INTO turn_deliveries (delivery_id, turn_id) SELECT id, ? FROM deliveries WHERE id = ? AND session_id = ?", open3.id, deliveryId2, session.id));
|
|
27369
27390
|
}
|
|
@@ -27441,9 +27462,14 @@ function fold(state, chunks, now = Date.now()) {
|
|
|
27441
27462
|
stmts.push(...notificationFor(messageId2, "agent", text, state.notify ?? void 0));
|
|
27442
27463
|
stmts.push(s(`INSERT OR IGNORE INTO message_attachments (message_id, position, upload_id, name, size)
|
|
27443
27464
|
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));
|
|
27444
|
-
const
|
|
27465
|
+
const onPost = text === "" ? state.lastPost : null;
|
|
27466
|
+
const to = [.../* @__PURE__ */ new Set([...recipients(state.roster, text, { name: session.bot, kind: "agent" }, session.thread, state.participants), ...onPost ? [] : handedBack(state, text, named)])];
|
|
27445
27467
|
stmts.push(...routingStatements(messageId2, session.thread, to, at, { by: session.bot, joins: true }));
|
|
27446
27468
|
deliver.push(messageId2);
|
|
27469
|
+
if (onPost) {
|
|
27470
|
+
stmts.push(...routingStatements(onPost, session.thread, handedBack(state, "", named), at, { by: session.bot, joins: true }));
|
|
27471
|
+
deliver.push(onPost);
|
|
27472
|
+
}
|
|
27447
27473
|
}
|
|
27448
27474
|
end("done");
|
|
27449
27475
|
break;
|