eve-hub 0.6.32 → 0.6.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # eve-hub
2
2
 
3
+ ## 0.6.34
4
+
5
+ ### Patch Changes
6
+
7
+ - Run on eve 0.81.1: the Dash schedule calls eve's `decide` (renamed from `evaluate`), the sandbox handle hooks are onSandboxStop/onSandboxDelete, and the `ai` peer range is ^7.0.137.
8
+
9
+ ## 0.6.33
10
+
11
+ ### Patch Changes
12
+
13
+ - Hosted hub: a schedule's run history is what its handler reports (hub.track: a start, an end and the error), so a dispatch is one run however often it loads state; a commit names its run. The mention rule moves to the kit (mention-tokens.mjs).
14
+
3
15
  ## 0.6.32
4
16
 
5
17
  ### Patch Changes
package/host-sandbox.d.ts CHANGED
@@ -39,8 +39,8 @@ export declare function hostEnvironment(options?: HostOptions): {
39
39
  worktree?: SessionWorktree;
40
40
  };
41
41
  onRuntimeShutdown: () => Promise<void>;
42
- onSessionStop: () => Promise<void>;
43
- onSessionDelete: () => Promise<void>;
42
+ onSandboxStop: () => Promise<void>;
43
+ onSandboxDelete: () => Promise<void>;
44
44
  };
45
45
  state: HostState;
46
46
  }>;
@@ -53,8 +53,8 @@ export declare function hostEnvironment(options?: HostOptions): {
53
53
  worktree?: SessionWorktree;
54
54
  };
55
55
  onRuntimeShutdown: () => Promise<void>;
56
- onSessionStop: () => Promise<void>;
57
- onSessionDelete: () => Promise<void>;
56
+ onSandboxStop: () => Promise<void>;
57
+ onSandboxDelete: () => Promise<void>;
58
58
  }>;
59
59
  };
60
60
  export declare const HostSandbox: import("eve/sandbox/provider").SandboxProvider<HostOptions, undefined, SandboxSession>;
@@ -65,8 +65,8 @@ export declare function hostHandle(rootOrState: string | HostState, sessionId: s
65
65
  worktree?: SessionWorktree;
66
66
  };
67
67
  onRuntimeShutdown: () => Promise<void>;
68
- onSessionStop: () => Promise<void>;
69
- onSessionDelete: () => Promise<void>;
68
+ onSandboxStop: () => Promise<void>;
69
+ onSandboxDelete: () => Promise<void>;
70
70
  };
71
71
  export declare function killTree(child: ChildProcess): void;
72
72
  declare const forkConfig: (sessionId: string) => Promise<{
package/host-sandbox.js CHANGED
@@ -123,8 +123,8 @@ export function hostHandle(rootOrState, sessionId) {
123
123
  return {
124
124
  sandbox: hostSession(root, children, sessionId, { worktree, rootSessionId }),
125
125
  onRuntimeShutdown: killAll,
126
- onSessionStop: killAll,
127
- onSessionDelete: killAll,
126
+ onSandboxStop: killAll,
127
+ onSandboxDelete: killAll,
128
128
  };
129
129
  }
130
130
  // SIGTERM the whole process group; whatever ignores it gets SIGKILL after
@@ -0,0 +1 @@
1
+ export function mentionTokens(text: unknown): string[];
@@ -0,0 +1,15 @@
1
+ import { codeRanges } from "./mention-gate.mjs";
2
+
3
+ // The one definition of an actual mention: `@handle` at a word boundary (not
4
+ // foo@bar, @@x, \@x or a URL's /@x) and outside code (codeRanges). Every reader of
5
+ // handles (routing, aliases, @all, notifications) takes its tokens from here; anything else
6
+ // is text. Returns the lowercased handles in order, duplicates kept.
7
+ export function mentionTokens(text) {
8
+ const s = String(text ?? "");
9
+ const code = codeRanges(s);
10
+ const out = [];
11
+ for (const match of s.matchAll(/(?<![\w@./\\-])@([a-z0-9][a-z0-9-]*)/gi)) {
12
+ if (!code.some(([a, b]) => match.index >= a && match.index < b)) out.push(match[1].toLowerCase());
13
+ }
14
+ return out;
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eve-hub",
3
- "version": "0.6.32",
3
+ "version": "0.6.34",
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",
@@ -37,6 +37,6 @@
37
37
  "@vercel/oidc": "4.0.0"
38
38
  },
39
39
  "dependencies": {
40
- "ai": "^7.0.122"
40
+ "ai": "^7.0.137"
41
41
  }
42
42
  }
@@ -12,6 +12,7 @@ import { runCompact } from "./run-compact.mjs";
12
12
  export default defineSchedule({
13
13
  cron: "*/10 * * * *",
14
14
  async run() {
15
- await runCompact(createHub());
15
+ const hub = createHub();
16
+ await hub.track("compact", () => runCompact(hub));
16
17
  },
17
18
  });
package/schedules/dash.js CHANGED
@@ -1,12 +1,12 @@
1
1
  // main's dash schedule: every 8 minutes, and off-cron as soon as a bot's turn
2
2
  // ends (the hub fires it, so a question shows on the sidebar within seconds),
3
3
  // the one pass behind the Dash: the human's PRs, a model call per thread
4
- // whose facts changed (openai/gpt-6-luna-decisions through eve's `evaluate`,
4
+ // whose facts changed (openai/gpt-6-luna-decisions through eve`s `decide`,
5
5
  // eve's gateway credentials) and a model call per track with news
6
6
  // (anthropic/claude-sonnet-5.5 through the AI SDK). The pass: runPass in
7
7
  // ./dash-pass.mjs.
8
8
  import { defineSchedule } from "eve/schedules";
9
- import { evaluate } from "eve/ai";
9
+ import { decide as evaluate } from "eve/ai";
10
10
  import { generateText } from "ai";
11
11
  import { createHub } from "./hub.mjs";
12
12
  import { modelClassify } from "./dash-classify.mjs";
@@ -15,6 +15,7 @@ import { modelSummarize } from "./track-summaries.mjs";
15
15
  export default defineSchedule({
16
16
  cron: "*/8 * * * *",
17
17
  async run() {
18
- await dashPass(createHub(), { classify: modelClassify({ evaluate }), summarize: modelSummarize({ generateText }) });
18
+ const hub = createHub();
19
+ await hub.track("dash", () => dashPass(hub, { classify: modelClassify({ evaluate }), summarize: modelSummarize({ generateText }) }));
19
20
  },
20
21
  });
@@ -9,10 +9,11 @@ export type Hub = {
9
9
  snapshot(): Promise<Record<string, unknown>>;
10
10
  roster(): Promise<unknown>;
11
11
  load(id: string): Promise<unknown>;
12
- loadVersioned(id: string): Promise<{ state: unknown; savedAt: string | null }>;
12
+ loadVersioned(id: string): Promise<{ state: unknown; savedAt: string | null; paused: boolean }>;
13
13
  save(id: string, state: unknown): Promise<unknown>;
14
14
  commit(id: string, withState: { state?: unknown }, posts: Post[]): Promise<unknown>;
15
15
  post(id: string, text: string, key?: string): Promise<unknown>;
16
+ track<T>(id: string, fn: () => Promise<T>): Promise<T | undefined>;
16
17
  compact(threadId: string, bot: string, by: string, report?: { trigger?: string; reportTo?: string }): Promise<{ status: number; ok: boolean; body: unknown }>;
17
18
  dashChecked(prFacts?: Record<string, { head: string | null; ci: string }>): Promise<Ok & { checkedAt: string }>;
18
19
  setSummary(path: string, text: string): Promise<unknown>;
package/schedules/hub.mjs CHANGED
@@ -23,6 +23,8 @@ export function createHub({ base = registryBase(), agent = "main", fetchImpl = f
23
23
  if (!r.ok) throw new Error(`${init?.method ?? "GET"} ${path}: ${r.status} ${typeof r.body === "string" ? r.body : JSON.stringify(r.body)}`.slice(0, 400));
24
24
  return r.body;
25
25
  };
26
+ // The run the hub is following for this process's handler (`track`), so a commit says which run it belongs to.
27
+ let run = null;
26
28
  const threadOf = (id) => `sch-${agent}-${id.replaceAll("/", ".")}`;
27
29
  const statePath = (id) => `/api/schedules/${encodeURIComponent(agent)}/${id.split("/").map(encodeURIComponent).join("/")}/state`;
28
30
  return {
@@ -32,11 +34,31 @@ export function createHub({ base = registryBase(), agent = "main", fetchImpl = f
32
34
  // The state and when it was saved (the version a run decided on).
33
35
  loadVersioned: async (id) => {
34
36
  const body = await must(statePath(id));
35
- return { state: body?.state ?? null, savedAt: body?.savedAt ?? null };
37
+ return { state: body?.state ?? null, savedAt: body?.savedAt ?? null, paused: body?.paused === true };
36
38
  },
37
39
  save: (id, state) => must(statePath(id), { method: "PUT", body: JSON.stringify({ state }) }),
38
40
  // Save `state` (if given: { state }) and post `posts` in one step.
39
- commit: (id, withState, posts) => must(statePath(id), { method: "PUT", body: JSON.stringify({ ...withState, posts }) }),
41
+ commit: (id, withState, posts) => must(statePath(id), { method: "PUT", body: JSON.stringify({ ...withState, posts, ...(run?.id === id ? { run: run.run } : {}) }) }),
42
+ // One dispatch of the schedule `id`: the hub is told it started (it answers with the run's id and whether the
43
+ // schedule is paused, which skips `fn`) and how it ended, so the hub's run history is what the handler did. A hub
44
+ // that doesn't keep run history (the local one) answers 404 to the start, and `fn` runs untracked.
45
+ track: async (id, fn) => {
46
+ const started = await call(`${statePath(id).slice(0, -"/state".length)}/runs`, { method: "POST", body: "{}" }).catch(() => null);
47
+ if (!started?.ok) return fn();
48
+ if (started.body.paused) return;
49
+ run = { id, run: started.body.run };
50
+ const end = (error) => call(`${statePath(id).slice(0, -"/state".length)}/runs/${encodeURIComponent(run.run)}/end`, { method: "POST", body: JSON.stringify(error ? { error: error.slice(0, 2000) } : {}) }).catch(() => null);
51
+ try {
52
+ const result = await fn();
53
+ await end();
54
+ return result;
55
+ } catch (error) {
56
+ await end(String(error?.message ?? error) || "the run failed");
57
+ throw error;
58
+ } finally {
59
+ run = null;
60
+ }
61
+ },
40
62
  // A one-off post outside a run (runOnce commits through `commit`).
41
63
  post: (id, text, key) => must(`/api/threads/${encodeURIComponent(threadOf(id))}/messages`, { method: "POST", body: JSON.stringify({ author: "system", text, ...(key ? { clientId: key } : {}) }) }),
42
64
  // With `report` ({ trigger, reportTo: "<bot>/<schedule>" }) the hub posts
@@ -117,10 +139,12 @@ export function toPosts(notices) {
117
139
  // the same keys and the hub skips the posts it already has. Nothing is
118
140
  // lost and nothing posted twice. `decide(snap, saved, now)` ->
119
141
  // { posts: [text], state, changed }; `snapshot: false` skips reading
120
- // /api/state (decide gets null).
142
+ // /api/state (decide gets null). A paused schedule (the state's `paused`)
143
+ // decides and commits nothing.
121
144
  export async function runOnce(hub, id, decide, { now = Date.now(), snapshot = true } = {}) {
122
145
  const [snap, loaded] = await Promise.all([snapshot ? hub.snapshot() : null, hub.loadVersioned(id)]);
123
146
  const saved = loaded.state;
147
+ if (loaded.paused) return { posts: [], state: saved };
124
148
  const { posts = [], state, changed } = await decide(snap, saved, now);
125
149
  const texts = toPosts(posts.filter((t) => typeof t === "string" && t.trim()));
126
150
  if (!changed && !texts.length) return { posts: [], state: saved };
package/tools/bash.js CHANGED
@@ -1,12 +1,11 @@
1
1
  import { defineTool } from "eve/tools";
2
- import { bash } from "eve/tools/bash";
3
2
  import { z } from "zod";
4
3
  import { MAX_TIMEOUT_S, bashDescription, runBash, workspaceCode } from "../bash-run.mjs";
5
4
  import { HOST_ROOT } from "../host-sandbox.js";
6
5
  // eve's bash, with a time limit and a pointer to the workspace's code (see
7
6
  // ../bash-run.mjs). Same output shape, plus `timedOut` when the limit fired.
8
7
  export default defineTool({
9
- label: bash.label,
8
+ label: { start: (input) => `Run ${input.command}`.trimEnd() },
10
9
  description: bashDescription({ hostRoot: HOST_ROOT }),
11
10
  inputSchema: z.object({
12
11
  command: z.string().describe("The shell command to execute."),
package/world.mjs CHANGED
@@ -545,6 +545,79 @@ function stepValues(sessionId2, step) {
545
545
  return [sessionId2, step.sequence, step.stepIndex, Date.parse(step.at), b.inputTokens, b.outputTokens, b.cacheReadTokens, b.cacheWriteTokens, b.costUsd];
546
546
  }
547
547
 
548
+ // mention-gate.mjs
549
+ function codeRanges(text) {
550
+ const s2 = String(text ?? "");
551
+ const ranges = [];
552
+ const lines = [];
553
+ for (let at = 0; at <= s2.length; ) {
554
+ const nl = s2.indexOf("\n", at);
555
+ const end = nl < 0 ? s2.length : nl;
556
+ lines.push({ start: at, end, text: s2.slice(at, end) });
557
+ if (nl < 0) break;
558
+ at = nl + 1;
559
+ }
560
+ const fenced = /* @__PURE__ */ new Set();
561
+ for (let i = 0; i < lines.length; i++) {
562
+ const open3 = lines[i].text.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
563
+ if (!open3 || open3[1][0] === "`" && open3[2].includes("`")) continue;
564
+ const close = new RegExp(`^ {0,3}${open3[1][0] === "`" ? "`" : "~"}{${open3[1].length},}\\s*$`);
565
+ const j = lines.findIndex((l, k) => k > i && close.test(l.text));
566
+ if (j < 0) continue;
567
+ ranges.push([lines[i].start, lines[j].end]);
568
+ for (let k = i; k <= j; k++) fenced.add(k);
569
+ i = j;
570
+ }
571
+ let para = [];
572
+ const flush = () => {
573
+ if (!para.length) return;
574
+ const from = para[0].start;
575
+ const t = s2.slice(from, para.at(-1).end);
576
+ const runs = [...t.matchAll(/`+/g)].map((m) => ({ at: m.index, n: m[0].length }));
577
+ for (let i = 0; i < runs.length; i++) {
578
+ let { at, n } = runs[i];
579
+ let slash = 0;
580
+ for (let b = at - 1; b >= 0 && t[b] === "\\"; b--) slash++;
581
+ if (slash % 2 === 1) {
582
+ at++;
583
+ n--;
584
+ if (!n) continue;
585
+ }
586
+ const j = runs.findIndex((r, k) => k > i && r.n === n);
587
+ if (j < 0) continue;
588
+ ranges.push([from + at, from + runs[j].at + n]);
589
+ i = j;
590
+ }
591
+ para = [];
592
+ };
593
+ lines.forEach((l, i) => {
594
+ if (!fenced.has(i) && l.text.trim()) para.push(l);
595
+ flush();
596
+ });
597
+ return ranges;
598
+ }
599
+
600
+ // mention-tokens.mjs
601
+ function mentionTokens(text) {
602
+ const s2 = String(text ?? "");
603
+ const code = codeRanges(s2);
604
+ const out = [];
605
+ for (const match of s2.matchAll(/(?<![\w@./\\-])@([a-z0-9][a-z0-9-]*)/gi)) {
606
+ if (!code.some(([a, b]) => match.index >= a && match.index < b)) out.push(match[1].toLowerCase());
607
+ }
608
+ return out;
609
+ }
610
+
611
+ // ../../hosted-hub/src/store/notifications.ts
612
+ var NOTIFICATION_READS = s("SELECT person, notification_id FROM notification_reads");
613
+ var NOTIFICATIONS = s("SELECT message_id, person FROM notifications");
614
+ var names = (text, { person, names: names2 }) => {
615
+ const known = new Set([person, ...names2].map((n) => n.toLowerCase()));
616
+ return mentionTokens(text).some((t) => known.has(t));
617
+ };
618
+ var record = (id, person) => s("INSERT OR IGNORE INTO notifications (message_id, person) SELECT ?, ? WHERE EXISTS (SELECT 1 FROM messages WHERE id = ?)", id, person, id);
619
+ var notificationFor = (messageId2, kind, text, notify) => notify && kind === "agent" && names(text, notify) ? [record(messageId2, notify.person)] : [];
620
+
548
621
  // ../../hosted-hub/src/store/subagents.ts
549
622
  var UP = `up(session_id, parent) AS (
550
623
  SELECT session_id, parent FROM subagent_links WHERE session_id = ?
@@ -828,7 +901,7 @@ __export(external_exports, {
828
901
  properties: () => _properties,
829
902
  property: () => _property,
830
903
  readonly: () => readonly,
831
- record: () => record,
904
+ record: () => record2,
832
905
  refine: () => refine,
833
906
  regex: () => _regex,
834
907
  regexes: () => regexes_exports,
@@ -17271,14 +17344,14 @@ function rewriteKeyNames(ctx) {
17271
17344
  bySchema.set(entry.schema, entry);
17272
17345
  }
17273
17346
  const rewrites = /* @__PURE__ */ new Map();
17274
- for (const record3 of pendingRecords.get(ctx) ?? []) {
17275
- const seen = ctx.seen.get(record3);
17276
- const names = (seen?.def ?? seen?.schema)?.propertyNames;
17277
- if (!names || names === true || rewrites.has(names))
17347
+ for (const record4 of pendingRecords.get(ctx) ?? []) {
17348
+ const seen = ctx.seen.get(record4);
17349
+ const names2 = (seen?.def ?? seen?.schema)?.propertyNames;
17350
+ if (!names2 || names2 === true || rewrites.has(names2))
17278
17351
  continue;
17279
- const rewritten = stringifyKeyNames(bySchema, names, /* @__PURE__ */ new Set());
17280
- if (rewritten !== names)
17281
- rewrites.set(names, rewritten);
17352
+ const rewritten = stringifyKeyNames(bySchema, names2, /* @__PURE__ */ new Set());
17353
+ if (rewritten !== names2)
17354
+ rewrites.set(names2, rewritten);
17282
17355
  }
17283
17356
  if (!rewrites.size)
17284
17357
  return;
@@ -17740,7 +17813,7 @@ __export(schemas_exports2, {
17740
17813
  preprocess: () => preprocess,
17741
17814
  promise: () => promise,
17742
17815
  readonly: () => readonly,
17743
- record: () => record,
17816
+ record: () => record2,
17744
17817
  refine: () => refine,
17745
17818
  set: () => set,
17746
17819
  strictObject: () => strictObject,
@@ -18842,7 +18915,7 @@ var ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
18842
18915
  inst.keyType = def.keyType;
18843
18916
  inst.valueType = def.valueType;
18844
18917
  });
18845
- function record(keyType, valueType, params) {
18918
+ function record2(keyType, valueType, params) {
18846
18919
  if (!valueType || !valueType._zod) {
18847
18920
  return new ZodRecord({
18848
18921
  type: "record",
@@ -19317,7 +19390,7 @@ var stringbool = (...args) => _stringbool({
19317
19390
  }, ...args);
19318
19391
  function json(params) {
19319
19392
  const jsonSchema = lazy(() => {
19320
- return union([string2(params), number2(), boolean2(), _null3(), array(jsonSchema), record(string2(), jsonSchema)]);
19393
+ return union([string2(params), number2(), boolean2(), _null3(), array(jsonSchema), record2(string2(), jsonSchema)]);
19321
19394
  });
19322
19395
  return jsonSchema;
19323
19396
  }
@@ -25254,14 +25327,14 @@ function rewriteKeyNames2(ctx) {
25254
25327
  bySchema.set(entry.schema, entry);
25255
25328
  }
25256
25329
  const rewrites = /* @__PURE__ */ new Map();
25257
- for (const record3 of pendingRecords2.get(ctx) ?? []) {
25258
- const seen = ctx.seen.get(record3);
25259
- const names = (seen?.def ?? seen?.schema)?.propertyNames;
25260
- if (!names || names === true || rewrites.has(names))
25330
+ for (const record4 of pendingRecords2.get(ctx) ?? []) {
25331
+ const seen = ctx.seen.get(record4);
25332
+ const names2 = (seen?.def ?? seen?.schema)?.propertyNames;
25333
+ if (!names2 || names2 === true || rewrites.has(names2))
25261
25334
  continue;
25262
- const rewritten = stringifyKeyNames2(bySchema, names, /* @__PURE__ */ new Set());
25263
- if (rewritten !== names)
25264
- rewrites.set(names, rewritten);
25335
+ const rewritten = stringifyKeyNames2(bySchema, names2, /* @__PURE__ */ new Set());
25336
+ if (rewritten !== names2)
25337
+ rewrites.set(names2, rewritten);
25265
25338
  }
25266
25339
  if (!rewrites.size)
25267
25340
  return;
@@ -26092,7 +26165,7 @@ var ZodRecord2 = /* @__PURE__ */ $constructor2("ZodRecord", (inst, def) => {
26092
26165
  inst.keyType = def.keyType;
26093
26166
  inst.valueType = def.valueType;
26094
26167
  });
26095
- function record2(keyType, valueType, params) {
26168
+ function record3(keyType, valueType, params) {
26096
26169
  if (!valueType || !valueType._zod) {
26097
26170
  return new ZodRecord2({
26098
26171
  type: "record",
@@ -26362,7 +26435,7 @@ function superRefine2(fn, params) {
26362
26435
 
26363
26436
  // ../wire/src/common.ts
26364
26437
  var maybe = (schema) => schema.nullish();
26365
- var map2 = (id, value) => record2(string5(), value).meta({ id });
26438
+ var map2 = (id, value) => record3(string5(), value).meta({ id });
26366
26439
 
26367
26440
  // ../wire/src/live.ts
26368
26441
  var TaskRow = object2({
@@ -27034,6 +27107,7 @@ var DELIVERY_STATUS = `CASE WHEN d.accepted_at IS NOT NULL OR ${LINKED} THEN COA
27034
27107
  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 ORDER BY position)) AS roster,
27035
27108
  (SELECT json_group_array(bot) FROM (${PARTICIPANTS("s.thread_id")})) AS participants,
27036
27109
  (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,
27110
+ (SELECT json_object('person', person, 'names', json(names)) FROM human_notify) AS notify,
27037
27111
  (SELECT json_object('status', t.status, 'id', t.id, 'text', t.reply_text, 'lastStep', json(t.last_step), 'activity', json(t.activity)) FROM turns t WHERE t.session_id = s.id AND t.status IN ${OPEN_TURN_STATUSES}
27038
27112
  ORDER BY t.started_at DESC LIMIT 1) AS open_turn
27039
27113
  FROM sessions s LEFT JOIN session_folds f ON f.session_id = s.id WHERE s.id = ?`;
@@ -27044,6 +27118,7 @@ function foldState(row) {
27044
27118
  roster: JSON.parse(String(row.roster)),
27045
27119
  participants: JSON.parse(String(row.participants)),
27046
27120
  held: row.held == null ? null : String(row.held),
27121
+ notify: row.notify ? JSON.parse(String(row.notify)) : null,
27047
27122
  foldedIndex: Number(row.folded_index),
27048
27123
  open: row.open_turn ? JSON.parse(String(row.open_turn)) : null
27049
27124
  };
@@ -27174,6 +27249,7 @@ function fold(state, chunks, now = Date.now()) {
27174
27249
  SELECT ?, ?, ?, 'agent', ?, ?, ? WHERE ? <> ''
27175
27250
  AND EXISTS (SELECT 1 FROM turns WHERE id = ? AND status NOT IN ('cancelling','cancelled') AND NOT (${REVOKED_TURN}))`, messageId2, session.thread, session.bot, text, open3.id, at, text, open3.id));
27176
27251
  log("message.created", { messageId: messageId2, author: session.bot, authorKind: "agent" }, IF_INSERTED);
27252
+ stmts.push(...notificationFor(messageId2, "agent", text, state.notify ?? void 0));
27177
27253
  const to = recipients(state.roster, text, { name: session.bot, kind: "agent" }, session.thread, state.participants);
27178
27254
  stmts.push(...routingStatements(messageId2, session.thread, to, at, { by: session.bot, joins: true }));
27179
27255
  deliver.push(messageId2);