eve-hub 0.6.51 → 0.6.53
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 +12 -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 +68 -40
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# eve-hub
|
|
2
2
|
|
|
3
|
+
## 0.6.53
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- The Update card never offers an older release than the workspace runs, and the upgrade route answers a workspace still being set up as an error.
|
|
8
|
+
|
|
9
|
+
## 0.6.52
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
3
15
|
## 0.6.51
|
|
4
16
|
|
|
5
17
|
### 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.53",
|
|
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");
|
|
@@ -27040,6 +27042,8 @@ var HostedUpdate = object2({
|
|
|
27040
27042
|
commit: maybe(string5()),
|
|
27041
27043
|
/** `updated`: the Vercel page of the deployment that made the move; absent for a move recorded before it was kept. */
|
|
27042
27044
|
deployment: maybe(string5()),
|
|
27045
|
+
/** `updated`: the move was a commit that is not an update to the hub's release (a memory or instructions edit); `commit` is it. */
|
|
27046
|
+
plain: boolean5().optional(),
|
|
27043
27047
|
/**
|
|
27044
27048
|
* `kind`: sign-in (the owner signs in again) | build (`url` is the failed build) | other. A failed build also carries
|
|
27045
27049
|
* what Vercel says of it once the hub has read it: the `step` that failed, the `sha` it built, and the `log`'s last lines.
|
|
@@ -27205,7 +27209,7 @@ var StoredHosted = HostedWorkspace.omit({ eveHub: true, commits: true, update: t
|
|
|
27205
27209
|
workspace: DeployedWorkspace.nullable().default(null),
|
|
27206
27210
|
attempt: Attempt.nullable().default(null),
|
|
27207
27211
|
upgrade: Upgrade.nullable().default(null),
|
|
27208
|
-
updated: external_exports.object({ to: external_exports.string(), at: external_exports.string(), commit: external_exports.string().nullable().default(null), deployment: external_exports.string().nullable().default(null) }).nullable().default(null)
|
|
27212
|
+
updated: external_exports.object({ to: external_exports.string(), at: external_exports.string(), commit: external_exports.string().nullable().default(null), deployment: external_exports.string().nullable().default(null), plain: external_exports.boolean().default(false) }).nullable().default(null)
|
|
27209
27213
|
});
|
|
27210
27214
|
var REPO_READ = s("SELECT metadata FROM workspace_repo WHERE id = 1");
|
|
27211
27215
|
|
|
@@ -27226,7 +27230,8 @@ var TASK_CARD = `(SELECT CASE WHEN json_array_length(json_extract(t.activity, '$
|
|
|
27226
27230
|
AND m.rowid = (SELECT MAX(x.rowid) FROM messages x WHERE x.turn_id = m.turn_id))`;
|
|
27227
27231
|
var FACT_COLUMNS = `status, status_from, metadata, verdict, decisions, tested, ${TASK_CARD} AS task_card, event`;
|
|
27228
27232
|
var LINKED = "EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = d.id)";
|
|
27229
|
-
var DELIVERY_STATUS = `CASE WHEN d.
|
|
27233
|
+
var DELIVERY_STATUS = `CASE WHEN json_extract(d.gate, '$.decision') = 'skip' THEN 'skipped: ' || json_extract(d.gate, '$.reason')
|
|
27234
|
+
WHEN d.accepted_at IS NOT NULL OR ${LINKED} THEN COALESCE((SELECT 'waiting: on your answer to its ' || r.kind || ' request'
|
|
27230
27235
|
FROM input_requests r JOIN sessions rs ON rs.id = r.session_id
|
|
27231
27236
|
WHERE r.session_id = d.session_id AND r.status = 'pending' AND rs.ended_at IS NULL
|
|
27232
27237
|
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 +27250,22 @@ var RESTARTED_TURNS_READ = s(`SELECT t.thread_id, t.bot, r.claimed_at FROM turns
|
|
|
27245
27250
|
AND NOT EXISTS (SELECT 1 FROM session_folds f WHERE f.session_id = t.session_id AND f.folded_at >= r.claimed_at)`);
|
|
27246
27251
|
|
|
27247
27252
|
// ../../hosted-hub/src/store/fold.ts
|
|
27253
|
+
function handedBack({ session, roster, asked = [], posted = [], notify }, text, named) {
|
|
27254
|
+
if (session.thread.startsWith("dm-")) return [];
|
|
27255
|
+
const known = new Set([...roster, ...notify ? [notify.person, ...notify.names] : [], "all"].map((n) => n.toLowerCase())), me = session.bot.toLowerCase();
|
|
27256
|
+
if ([text, ...posted].some((t) => mentionTokens(t).some((name) => name !== me && known.has(name)))) return [];
|
|
27257
|
+
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))];
|
|
27258
|
+
}
|
|
27248
27259
|
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
27260
|
(SELECT json_group_array(bot) FROM (${PARTICIPANTS("s.thread_id")})) AS participants,
|
|
27250
27261
|
(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,
|
|
27262
|
+
(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})))
|
|
27263
|
+
FROM deliveries dd JOIN messages m ON m.id = dd.message_id WHERE dd.session_id = s.id AND m.author_kind = 'agent'
|
|
27264
|
+
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,
|
|
27265
|
+
(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)
|
|
27266
|
+
AND p.author = s.bot AND p.author_kind = 'agent' AND p.status IS NULL) AS posted,
|
|
27267
|
+
(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)
|
|
27268
|
+
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
27269
|
(SELECT json_object('person', person, 'names', json(names)) FROM human_notify) AS notify,
|
|
27252
27270
|
(SELECT json_object('status', t.status, 'id', t.id, 'text', t.reply_text, 'lastStep', json(t.last_step), 'activity', json(t.activity),
|
|
27253
27271
|
'trigger', ${triggerOf("t")}) FROM turns t WHERE t.session_id = s.id AND t.status IN ${OPEN_TURN_STATUSES}
|
|
@@ -27262,6 +27280,9 @@ function foldState(row) {
|
|
|
27262
27280
|
roster: JSON.parse(String(row.roster)),
|
|
27263
27281
|
participants: JSON.parse(String(row.participants)),
|
|
27264
27282
|
held: row.held == null ? null : String(row.held),
|
|
27283
|
+
asked: JSON.parse(String(row.asked ?? "[]")),
|
|
27284
|
+
posted: JSON.parse(String(row.posted ?? "[]")),
|
|
27285
|
+
lastPost: row.last_post == null ? null : String(row.last_post),
|
|
27265
27286
|
notify: row.notify ? JSON.parse(String(row.notify)) : null,
|
|
27266
27287
|
foldedIndex: Number(row.folded_index),
|
|
27267
27288
|
open: row.open_turn ? JSON.parse(String(row.open_turn)) : null,
|
|
@@ -27275,7 +27296,7 @@ var IF_INSERTED = "WHERE changes() > 0";
|
|
|
27275
27296
|
var lastActWasOwnPost = (ls) => !!ls && Object.keys(ls.calls).length > 0 && Object.values(ls.calls).every((own3) => own3 === true);
|
|
27276
27297
|
function fold(state, chunks, now = Date.now()) {
|
|
27277
27298
|
const { session } = state;
|
|
27278
|
-
const stmts = [], touched = /* @__PURE__ */ new Map();
|
|
27299
|
+
const stmts = [], touched = /* @__PURE__ */ new Map(), named = /* @__PURE__ */ new Set();
|
|
27279
27300
|
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
27301
|
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
27302
|
const deliver = [];
|
|
@@ -27363,7 +27384,9 @@ function fold(state, chunks, now = Date.now()) {
|
|
|
27363
27384
|
data.message.length - state2
|
|
27364
27385
|
));
|
|
27365
27386
|
}
|
|
27387
|
+
if (event.type === "turn.started") named.clear();
|
|
27366
27388
|
for (const deliveryId2 of event.meta?.deliveryIds ?? []) {
|
|
27389
|
+
named.add(deliveryId2);
|
|
27367
27390
|
open3.trigger ??= messageOfDelivery(deliveryId2, session.bot);
|
|
27368
27391
|
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
27392
|
}
|
|
@@ -27441,9 +27464,14 @@ function fold(state, chunks, now = Date.now()) {
|
|
|
27441
27464
|
stmts.push(...notificationFor(messageId2, "agent", text, state.notify ?? void 0));
|
|
27442
27465
|
stmts.push(s(`INSERT OR IGNORE INTO message_attachments (message_id, position, upload_id, name, size)
|
|
27443
27466
|
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
|
|
27467
|
+
const onPost = text === "" ? state.lastPost : null;
|
|
27468
|
+
const to = [.../* @__PURE__ */ new Set([...recipients(state.roster, text, { name: session.bot, kind: "agent" }, session.thread, state.participants), ...onPost ? [] : handedBack(state, text, named)])];
|
|
27445
27469
|
stmts.push(...routingStatements(messageId2, session.thread, to, at, { by: session.bot, joins: true }));
|
|
27446
27470
|
deliver.push(messageId2);
|
|
27471
|
+
if (onPost) {
|
|
27472
|
+
stmts.push(...routingStatements(onPost, session.thread, handedBack(state, "", named), at, { by: session.bot, joins: true }));
|
|
27473
|
+
deliver.push(onPost);
|
|
27474
|
+
}
|
|
27447
27475
|
}
|
|
27448
27476
|
end("done");
|
|
27449
27477
|
break;
|