eve-hub 0.6.31 → 0.6.33

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.33
4
+
5
+ ### Patch Changes
6
+
7
+ - 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).
8
+
9
+ ## 0.6.32
10
+
11
+ ### Patch Changes
12
+
13
+ - The hosted bots' world carries the hub's image attachments: uploads are stored and handed to bots with a message.
14
+
3
15
  ## 0.6.31
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,5 @@
1
+ export const MAX_BYTES: number;
2
+ export const MODEL_MAX_EDGE: number;
3
+ export function sniffImage(bytes: Uint8Array): string | null;
4
+ export function imageSize(bytes: Uint8Array, mediaType: string): { width: number; height: number } | null;
5
+ export function modelUnsafe(bytes: Uint8Array): string | null;
@@ -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.31",
3
+ "version": "0.6.33",
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",
@@ -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
@@ -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 };
@@ -13,7 +13,7 @@ export declare function prepareImage(bytes: Uint8Array, name: string, { resize }
13
13
  resize?: Resize;
14
14
  }): Promise<{
15
15
  name: string;
16
- mediaType: any;
16
+ mediaType: string;
17
17
  original: string | undefined;
18
18
  unsafe: string;
19
19
  data?: undefined;
@@ -23,7 +23,7 @@ export declare function prepareImage(bytes: Uint8Array, name: string, { resize }
23
23
  unsafe?: undefined;
24
24
  data: string;
25
25
  name: string;
26
- mediaType: any;
26
+ mediaType: string;
27
27
  bytes: number;
28
28
  original: string | undefined;
29
29
  shown: string | undefined;
@@ -35,7 +35,7 @@ declare const _default: import("eve/tools").ToolDefinition<{
35
35
  screenshot?: boolean | undefined;
36
36
  }, {
37
37
  name: string;
38
- mediaType: any;
38
+ mediaType: string;
39
39
  original: string | undefined;
40
40
  unsafe: string;
41
41
  data?: undefined;
@@ -45,7 +45,7 @@ declare const _default: import("eve/tools").ToolDefinition<{
45
45
  unsafe?: undefined;
46
46
  data: string;
47
47
  name: string;
48
- mediaType: any;
48
+ mediaType: string;
49
49
  bytes: number;
50
50
  original: string | undefined;
51
51
  shown: string | undefined;
@@ -55,7 +55,7 @@ declare const _default: import("eve/tools").ToolDefinition<{
55
55
  screenshot?: boolean | undefined;
56
56
  }, ctx: import("eve/tools").ToolContext): Promise<{
57
57
  name: string;
58
- mediaType: any;
58
+ mediaType: string;
59
59
  original: string | undefined;
60
60
  unsafe: string;
61
61
  data?: undefined;
@@ -65,7 +65,7 @@ declare const _default: import("eve/tools").ToolDefinition<{
65
65
  unsafe?: undefined;
66
66
  data: string;
67
67
  name: string;
68
- mediaType: any;
68
+ mediaType: string;
69
69
  bytes: number;
70
70
  original: string | undefined;
71
71
  shown: string | undefined;
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 = ?
@@ -596,6 +669,14 @@ var PENDING_REQUESTS_READ = s(`SELECT r.id, r.session_id, r.thread_id, r.bot, r.
596
669
  // ../../hosted-hub/src/store/workspace-state.ts
597
670
  var LIVE_TURNS_READ = s(`SELECT thread_id,bot,started_at,reply_text,activity FROM turns WHERE status IN ${OPEN_TURN_STATUSES} ORDER BY started_at`);
598
671
 
672
+ // ../../hosted-hub/src/store/attachments.ts
673
+ var ATTACHMENTS_READ = s("SELECT message_id, upload_id, name FROM message_attachments ORDER BY message_id, position");
674
+ var unseenBy = (bot, thread, mid) => {
675
+ const end = `COALESCE((SELECT rowid FROM messages WHERE id = ${mid}), 9223372036854775807)`;
676
+ return `a.message_id = e.id AND e.thread_id = ${thread} AND e.author <> ${bot} AND e.rowid < ${end}
677
+ AND e.rowid > COALESCE((SELECT MAX(p.rowid) FROM deliveries dd JOIN messages p ON p.id = dd.message_id WHERE p.thread_id = e.thread_id AND dd.bot = ${bot} AND p.rowid < ${end}), 0)`;
678
+ };
679
+
599
680
  // ../../hosted-hub/src/store/threads.ts
600
681
  var SUMMARY_READ = s("SELECT scope,text,by,at FROM summaries");
601
682
 
@@ -820,7 +901,7 @@ __export(external_exports, {
820
901
  properties: () => _properties,
821
902
  property: () => _property,
822
903
  readonly: () => readonly,
823
- record: () => record,
904
+ record: () => record2,
824
905
  refine: () => refine,
825
906
  regex: () => _regex,
826
907
  regexes: () => regexes_exports,
@@ -17263,14 +17344,14 @@ function rewriteKeyNames(ctx) {
17263
17344
  bySchema.set(entry.schema, entry);
17264
17345
  }
17265
17346
  const rewrites = /* @__PURE__ */ new Map();
17266
- for (const record3 of pendingRecords.get(ctx) ?? []) {
17267
- const seen = ctx.seen.get(record3);
17268
- const names = (seen?.def ?? seen?.schema)?.propertyNames;
17269
- 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))
17270
17351
  continue;
17271
- const rewritten = stringifyKeyNames(bySchema, names, /* @__PURE__ */ new Set());
17272
- if (rewritten !== names)
17273
- rewrites.set(names, rewritten);
17352
+ const rewritten = stringifyKeyNames(bySchema, names2, /* @__PURE__ */ new Set());
17353
+ if (rewritten !== names2)
17354
+ rewrites.set(names2, rewritten);
17274
17355
  }
17275
17356
  if (!rewrites.size)
17276
17357
  return;
@@ -17732,7 +17813,7 @@ __export(schemas_exports2, {
17732
17813
  preprocess: () => preprocess,
17733
17814
  promise: () => promise,
17734
17815
  readonly: () => readonly,
17735
- record: () => record,
17816
+ record: () => record2,
17736
17817
  refine: () => refine,
17737
17818
  set: () => set,
17738
17819
  strictObject: () => strictObject,
@@ -18834,7 +18915,7 @@ var ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
18834
18915
  inst.keyType = def.keyType;
18835
18916
  inst.valueType = def.valueType;
18836
18917
  });
18837
- function record(keyType, valueType, params) {
18918
+ function record2(keyType, valueType, params) {
18838
18919
  if (!valueType || !valueType._zod) {
18839
18920
  return new ZodRecord({
18840
18921
  type: "record",
@@ -19309,7 +19390,7 @@ var stringbool = (...args) => _stringbool({
19309
19390
  }, ...args);
19310
19391
  function json(params) {
19311
19392
  const jsonSchema = lazy(() => {
19312
- 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)]);
19313
19394
  });
19314
19395
  return jsonSchema;
19315
19396
  }
@@ -25246,14 +25327,14 @@ function rewriteKeyNames2(ctx) {
25246
25327
  bySchema.set(entry.schema, entry);
25247
25328
  }
25248
25329
  const rewrites = /* @__PURE__ */ new Map();
25249
- for (const record3 of pendingRecords2.get(ctx) ?? []) {
25250
- const seen = ctx.seen.get(record3);
25251
- const names = (seen?.def ?? seen?.schema)?.propertyNames;
25252
- 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))
25253
25334
  continue;
25254
- const rewritten = stringifyKeyNames2(bySchema, names, /* @__PURE__ */ new Set());
25255
- if (rewritten !== names)
25256
- rewrites.set(names, rewritten);
25335
+ const rewritten = stringifyKeyNames2(bySchema, names2, /* @__PURE__ */ new Set());
25336
+ if (rewritten !== names2)
25337
+ rewrites.set(names2, rewritten);
25257
25338
  }
25258
25339
  if (!rewrites.size)
25259
25340
  return;
@@ -26084,7 +26165,7 @@ var ZodRecord2 = /* @__PURE__ */ $constructor2("ZodRecord", (inst, def) => {
26084
26165
  inst.keyType = def.keyType;
26085
26166
  inst.valueType = def.valueType;
26086
26167
  });
26087
- function record2(keyType, valueType, params) {
26168
+ function record3(keyType, valueType, params) {
26088
26169
  if (!valueType || !valueType._zod) {
26089
26170
  return new ZodRecord2({
26090
26171
  type: "record",
@@ -26354,7 +26435,7 @@ function superRefine2(fn, params) {
26354
26435
 
26355
26436
  // ../wire/src/common.ts
26356
26437
  var maybe = (schema) => schema.nullish();
26357
- var map2 = (id, value) => record2(string5(), value).meta({ id });
26438
+ var map2 = (id, value) => record3(string5(), value).meta({ id });
26358
26439
 
26359
26440
  // ../wire/src/live.ts
26360
26441
  var TaskRow = object2({
@@ -26999,6 +27080,11 @@ var SESSION_WORK_DELIVERY = `busy.accepted_at IS NOT NULL AND busy.failed_at IS
26999
27080
  WHERE td.delivery_id = busy.id AND t.status NOT IN ('streaming','cancelling'))`;
27000
27081
  var SESSION_BUSY = `s.ended_at IS NULL AND EXISTS (SELECT 1 FROM deliveries busy
27001
27082
  WHERE busy.session_id = s.id AND ${SESSION_WORK_DELIVERY})`;
27083
+ var PENDING_FROM = `SELECT d.id, d.message_id, d.session_id, d.bot, m.thread_id, t.title, m.author, m.author_kind, m.text, m.status, m.status_from, m.metadata, d.steer,
27084
+ EXISTS (SELECT 1 FROM deliveries a WHERE a.session_id = d.session_id AND a.accepted_at IS NOT NULL) AS session_started,
27085
+ s.ended_at IS NOT NULL AS session_ended, ${onLocal("d.bot")} AS on_local,
27086
+ (SELECT json_group_array(json_object('id', a.upload_id, 'name', a.name, 'size', a.size)) FROM (SELECT * FROM message_attachments WHERE message_id = m.id ORDER BY position) a) AS attachments
27087
+ FROM deliveries d JOIN messages m ON m.id = d.message_id JOIN threads t ON t.id = m.thread_id JOIN sessions s ON s.id = d.session_id`;
27002
27088
  var TASK_CARD = `(SELECT CASE WHEN json_array_length(json_extract(t.activity, '$.taskCard.tasks')) > 0 THEN json_extract(t.activity, '$.taskCard') END
27003
27089
  FROM turns t WHERE t.id = m.turn_id AND m.author_kind = 'agent' AND t.status NOT IN ${OPEN_TURN_STATUSES}
27004
27090
  AND m.rowid = (SELECT MAX(x.rowid) FROM messages x WHERE x.turn_id = m.turn_id))`;
@@ -27010,12 +27096,18 @@ var DELIVERY_STATUS = `CASE WHEN d.accepted_at IS NOT NULL OR ${LINKED} THEN COA
27010
27096
  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),
27011
27097
  CASE WHEN ${LINKED} THEN 'seen' ELSE 'delivered' END)
27012
27098
  WHEN d.failed_at IS NOT NULL THEN 'failed' WHEN d.revoked_at IS NOT NULL THEN 'skipped'
27013
- WHEN d.held_at IS NOT NULL THEN CASE d.held_scope WHEN 'bot' THEN 'queued until free' ELSE 'queued' END ELSE 'pending' END`;
27099
+ WHEN d.held_at IS NOT NULL THEN CASE d.held_scope WHEN 'bot' THEN 'queued until free' ELSE 'queued' END
27100
+ WHEN ${onLocal("d.bot")} AND EXISTS (SELECT 1 FROM local_runner WHERE id = 1 AND device IS NOT NULL AND attachments = 0)
27101
+ AND (EXISTS (SELECT 1 FROM message_attachments o WHERE o.message_id = d.message_id)
27102
+ OR EXISTS (SELECT 1 FROM message_attachments a, messages e WHERE ${unseenBy("d.bot", "(SELECT thread_id FROM messages WHERE id = d.message_id)", "d.message_id")}))
27103
+ THEN 'waiting: the Mac app running this bot is too old to carry images; update it'
27104
+ ELSE 'pending' END`;
27014
27105
 
27015
27106
  // ../../hosted-hub/src/store/fold.ts
27016
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,
27017
27108
  (SELECT json_group_array(bot) FROM (${PARTICIPANTS("s.thread_id")})) AS participants,
27018
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,
27019
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}
27020
27112
  ORDER BY t.started_at DESC LIMIT 1) AS open_turn
27021
27113
  FROM sessions s LEFT JOIN session_folds f ON f.session_id = s.id WHERE s.id = ?`;
@@ -27026,6 +27118,7 @@ function foldState(row) {
27026
27118
  roster: JSON.parse(String(row.roster)),
27027
27119
  participants: JSON.parse(String(row.participants)),
27028
27120
  held: row.held == null ? null : String(row.held),
27121
+ notify: row.notify ? JSON.parse(String(row.notify)) : null,
27029
27122
  foldedIndex: Number(row.folded_index),
27030
27123
  open: row.open_turn ? JSON.parse(String(row.open_turn)) : null
27031
27124
  };
@@ -27156,6 +27249,7 @@ function fold(state, chunks, now = Date.now()) {
27156
27249
  SELECT ?, ?, ?, 'agent', ?, ?, ? WHERE ? <> ''
27157
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));
27158
27251
  log("message.created", { messageId: messageId2, author: session.bot, authorKind: "agent" }, IF_INSERTED);
27252
+ stmts.push(...notificationFor(messageId2, "agent", text, state.notify ?? void 0));
27159
27253
  const to = recipients(state.roster, text, { name: session.bot, kind: "agent" }, session.thread, state.participants);
27160
27254
  stmts.push(...routingStatements(messageId2, session.thread, to, at, { by: session.bot, joins: true }));
27161
27255
  deliver.push(messageId2);