eve-hub 0.6.35 → 0.6.36

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,11 @@
1
1
  # eve-hub
2
2
 
3
+ ## 0.6.36
4
+
5
+ ### Patch Changes
6
+
7
+ - The bots' /hub/manifest reports the workspace's `hopLimit`, so the hosted hub holds bot-to-bot chains at it.
8
+
3
9
  ## 0.6.35
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eve-hub",
3
- "version": "0.6.35",
3
+ "version": "0.6.36",
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/prepare.mjs CHANGED
@@ -63,8 +63,8 @@ import workspace from "../../../../hub.manifest.json" with { type: "json" };
63
63
  // syncs by; a deployment with none (\`vercel deploy\` uploads no git metadata)
64
64
  // has no manifest to give.
65
65
  const sha = process.env.VERCEL_GIT_COMMIT_SHA;
66
- const { agents, humans, subagentCap } = parseWorkspace(workspace);
67
- const manifest = { sha, kit: ${JSON.stringify(KIT_VERSION)}, agents, humans, subagentCap };
66
+ const { agents, humans, subagentCap, hopLimit } = parseWorkspace(workspace);
67
+ const manifest = { sha, kit: ${JSON.stringify(KIT_VERSION)}, agents, humans, subagentCap, ...(hopLimit === undefined ? {} : { hopLimit }) };
68
68
 
69
69
  export default defineChannel({
70
70
  routes: [
package/world.mjs CHANGED
@@ -437,6 +437,7 @@ function derivedMessageId(...parts) {
437
437
  }
438
438
  var sessionId = () => `wrun_${ulid()}`;
439
439
  var deliveryId = (message, bot) => `dlv_${message}_${bot}`;
440
+ var messageOfDelivery = (delivery, bot) => delivery.slice("dlv_".length, -`_${bot}`.length);
440
441
  var turnId = (session, chunkIndex) => `trn_${session}_${chunkIndex}`;
441
442
 
442
443
  // ../../hosted-hub/src/store/events.ts
@@ -495,6 +496,9 @@ function routingStatements(messageId2, threadId2, bots, now, { by, joins }, held
495
496
  ])
496
497
  ];
497
498
  }
499
+ var hopAfter = (session) => s(`(SELECT 1 + COALESCE(MAX(m.hop), 0) FROM deliveries d JOIN messages m ON m.id = d.message_id WHERE d.session_id = ? AND (
500
+ EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = d.id AND td.turn_id = (SELECT id FROM turns WHERE session_id = ? ORDER BY started_at DESC, rowid DESC LIMIT 1))
501
+ OR (d.accepted_at IS NOT NULL AND NOT EXISTS (SELECT 1 FROM turn_deliveries td WHERE td.delivery_id = d.id))))`, session, session);
498
502
 
499
503
  // subagent-cap.mjs
500
504
  var DEFAULT_SUBAGENT_CAP = Object.freeze({
@@ -505,6 +509,8 @@ var DEFAULT_SUBAGENT_CAP = Object.freeze({
505
509
 
506
510
  // ../../hosted-hub/src/store/roster.ts
507
511
  var onLocal = (bot) => `EXISTS (SELECT 1 FROM bots WHERE name = ${bot} AND runs_on = 'local')`;
512
+ var HOP_LIMIT = "(SELECT json_extract(metadata, '$.workspace.hopLimit') FROM workspace_repo WHERE id = 1)";
513
+ var heldByHopLimit = (m) => `(${m}.author_kind = 'agent' AND ${HOP_LIMIT} IS NOT NULL AND ${m}.hop >= ${HOP_LIMIT})`;
508
514
 
509
515
  // ../../hosted-hub/src/store/schema.ts
510
516
  var OPEN_TURN_STATUSES = "('streaming','cancelling')";
@@ -635,6 +641,29 @@ function linkStatements(sessionId2, parent, callId) {
635
641
  ];
636
642
  }
637
643
 
644
+ // ../../hosted-hub/src/store/failures.ts
645
+ var STATE = "json_extract(event, '$.state')";
646
+ var failureId = (trigger, bot) => `evt_fail_${trigger}_${bot}`;
647
+ var cardLog = (idSql, now, ...args) => s(`INSERT INTO log (thread_id, type, data, at) SELECT thread_id, 'message.updated', json_object('threadId', thread_id, 'messageId', id), ? FROM messages WHERE id = (${idSql}) AND changes() > 0`, now, ...args);
648
+ function failureStatements(t, now) {
649
+ if (!t.trigger) return [];
650
+ const id = failureId(t.trigger, t.bot), detail = t.error ?? "unknown error";
651
+ const turn = (status) => s("EXISTS (SELECT 1 FROM turns WHERE id = ? AND status = ?)", t.id, status);
652
+ const failed = turn("failed"), settled = (status, next) => {
653
+ const when = turn(status);
654
+ return [s(`UPDATE messages SET event = json_set(event, '$.state', ?) WHERE id = ? AND ${STATE} = 'retrying' AND ${when.sql}`, next, id, ...when.args), cardLog("?", now, id)];
655
+ };
656
+ return [
657
+ ...eventStatements(t.threadId, { kind: "turn_failed", agent: t.bot, triggerId: t.trigger, state: "failed", retries: 0, turnId: t.id, detail }, `${t.bot}'s turn failed: ${detail}`, now, id, failed),
658
+ s(`UPDATE messages SET text = ?, event = json_set(event, '$.state', 'failed', '$.detail', ?, '$.turnId', ?, '$.retries', json_extract(event, '$.retries') + 1)
659
+ WHERE id = ? AND ${STATE} = 'retrying' AND json_extract(event, '$.turnId') <> ? AND ${failed.sql}`, `${t.bot}'s turn failed: ${detail}`, detail, t.id, id, t.id, ...failed.args),
660
+ cardLog("?", now, id),
661
+ ...settled("done", "resolved"),
662
+ ...settled("cancelled", "failed")
663
+ ];
664
+ }
665
+ var triggerOf = (t) => `(SELECT d.message_id FROM turn_deliveries td JOIN deliveries d ON d.id = td.delivery_id WHERE td.turn_id = ${t}.id ORDER BY d.rowid LIMIT 1)`;
666
+
638
667
  // ../../lib/tags.mjs
639
668
  var TAG_FIELDS = ["project", "issueType"];
640
669
  var LABEL_FIELDS = [...TAG_FIELDS, "path"];
@@ -5748,7 +5777,7 @@ var $ZodCyclicError = class extends Error {
5748
5777
  this.name = "ZodCyclicError";
5749
5778
  }
5750
5779
  };
5751
- var STATE = "~memo";
5780
+ var STATE2 = "~memo";
5752
5781
  var NO_ISSUES = [];
5753
5782
  function isRef(value) {
5754
5783
  return value !== null && typeof value === "object";
@@ -5962,10 +5991,10 @@ var memo = {
5962
5991
  const input2 = payload.value;
5963
5992
  if (!isRef(input2))
5964
5993
  return base(payload, ctx);
5965
- let state = ctx[STATE];
5994
+ let state = ctx[STATE2];
5966
5995
  if (!state) {
5967
5996
  state = { buckets: /* @__PURE__ */ new WeakMap(), backEdges: void 0 };
5968
- ctx[STATE] = state;
5997
+ ctx[STATE2] = state;
5969
5998
  }
5970
5999
  let bucket;
5971
6000
  if (lastCtx === ctx) {
@@ -6014,7 +6043,7 @@ function memoizer() {
6014
6043
  return memo;
6015
6044
  }
6016
6045
  function isBackEdge(ctx, value) {
6017
- const backEdges = ctx[STATE]?.backEdges;
6046
+ const backEdges = ctx[STATE2]?.backEdges;
6018
6047
  return backEdges !== void 0 && isRef(value) && backEdges.has(value);
6019
6048
  }
6020
6049
 
@@ -23602,7 +23631,7 @@ var $ZodCyclicError2 = class extends Error {
23602
23631
  this.name = "ZodCyclicError";
23603
23632
  }
23604
23633
  };
23605
- var STATE2 = "~memo";
23634
+ var STATE3 = "~memo";
23606
23635
  var NO_ISSUES2 = [];
23607
23636
  function cloneIssues2(issues) {
23608
23637
  return issues.map((iss) => iss.path ? { ...iss, path: iss.path.slice() } : { ...iss });
@@ -23777,10 +23806,10 @@ var memo2 = {
23777
23806
  const input2 = payload.value;
23778
23807
  if (input2 === null || typeof input2 !== "object")
23779
23808
  return base(payload, ctx);
23780
- let state = ctx[STATE2];
23809
+ let state = ctx[STATE3];
23781
23810
  if (!state) {
23782
23811
  state = { buckets: /* @__PURE__ */ new Map(), backEdges: void 0 };
23783
- ctx[STATE2] = state;
23812
+ ctx[STATE3] = state;
23784
23813
  }
23785
23814
  let bucket;
23786
23815
  if (lastCtx === ctx) {
@@ -23829,7 +23858,7 @@ function memoizer2() {
23829
23858
  return memo2;
23830
23859
  }
23831
23860
  function isBackEdge2(ctx, value) {
23832
- const backEdges = ctx[STATE2]?.backEdges;
23861
+ const backEdges = ctx[STATE3]?.backEdges;
23833
23862
  return backEdges !== void 0 && value !== null && typeof value === "object" && backEdges.has(value);
23834
23863
  }
23835
23864
 
@@ -27054,7 +27083,8 @@ var Cap = Requested.extend({ models: external_exports.array(external_exports.str
27054
27083
  var DeployedWorkspace = external_exports.object({
27055
27084
  agents: external_exports.array(external_exports.looseObject({ name: external_exports.string(), role: external_exports.string(), color: external_exports.string(), model: external_exports.string(), runsOn: external_exports.enum(["hosted", "local"]).optional(), subagents: external_exports.record(external_exports.string(), Requested).optional(), subagentCap: Cap.optional() })),
27056
27085
  humans: external_exports.array(external_exports.looseObject({ name: external_exports.string(), color: external_exports.string().optional() })).optional(),
27057
- subagentCap: Cap.optional()
27086
+ subagentCap: Cap.optional(),
27087
+ hopLimit: external_exports.int().min(1).nullable().optional()
27058
27088
  });
27059
27089
  var Upgrade = external_exports.object({
27060
27090
  to: external_exports.string(),
@@ -27087,6 +27117,7 @@ var PENDING_FROM = `SELECT d.id, d.message_id, d.session_id, d.bot, m.thread_id,
27087
27117
  s.ended_at IS NOT NULL AS session_ended, ${onLocal("d.bot")} AS on_local,
27088
27118
  (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
27089
27119
  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`;
27120
+ var UNSETTLED = `d.accepted_at IS NULL AND d.failed_at IS NULL AND d.revoked_at IS NULL AND d.held_at IS NULL AND NOT ${heldByHopLimit("m")}`;
27090
27121
  var TASK_CARD = `(SELECT CASE WHEN json_array_length(json_extract(t.activity, '$.taskCard.tasks')) > 0 THEN json_extract(t.activity, '$.taskCard') END
27091
27122
  FROM turns t WHERE t.id = m.turn_id AND m.author_kind = 'agent' AND t.status NOT IN ${OPEN_TURN_STATUSES}
27092
27123
  AND m.rowid = (SELECT MAX(x.rowid) FROM messages x WHERE x.turn_id = m.turn_id))`;
@@ -27097,7 +27128,7 @@ var DELIVERY_STATUS = `CASE WHEN d.accepted_at IS NOT NULL OR ${LINKED} THEN COA
27097
27128
  WHERE r.session_id = d.session_id AND r.status = 'pending' AND rs.ended_at IS NULL
27098
27129
  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),
27099
27130
  CASE WHEN ${LINKED} THEN 'seen' ELSE 'delivered' END)
27100
- WHEN d.failed_at IS NOT NULL THEN 'failed' WHEN d.revoked_at IS NOT NULL THEN 'skipped'
27131
+ WHEN d.failed_at IS NOT NULL THEN 'failed' WHEN d.revoked_at IS NOT NULL THEN 'skipped' WHEN EXISTS (SELECT 1 FROM messages hm WHERE hm.id = d.message_id AND ${heldByHopLimit("hm")}) THEN 'blocked: hop limit'
27101
27132
  WHEN d.held_at IS NOT NULL THEN CASE d.held_scope WHEN 'bot' THEN 'queued until free' ELSE 'queued' END
27102
27133
  WHEN ${onLocal("d.bot")} AND EXISTS (SELECT 1 FROM local_runner WHERE id = 1 AND device IS NOT NULL AND attachments = 0)
27103
27134
  AND (EXISTS (SELECT 1 FROM message_attachments o WHERE o.message_id = d.message_id)
@@ -27105,12 +27136,18 @@ var DELIVERY_STATUS = `CASE WHEN d.accepted_at IS NOT NULL OR ${LINKED} THEN COA
27105
27136
  THEN 'waiting: the Mac app running this bot is too old to carry images; update it'
27106
27137
  ELSE 'pending' END`;
27107
27138
 
27139
+ // ../../hosted-hub/src/store/local-runner.ts
27140
+ var RESTARTED_TURNS_READ = s(`SELECT t.thread_id, t.bot, r.claimed_at FROM turns t, local_runner r
27141
+ WHERE r.id = 1 AND t.status IN ${OPEN_TURN_STATUSES} AND ${onLocal("t.bot")} AND t.started_at < r.claimed_at
27142
+ AND NOT EXISTS (SELECT 1 FROM session_folds f WHERE f.session_id = t.session_id AND f.folded_at >= r.claimed_at)`);
27143
+
27108
27144
  // ../../hosted-hub/src/store/fold.ts
27109
27145
  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,
27110
27146
  (SELECT json_group_array(bot) FROM (${PARTICIPANTS("s.thread_id")})) AS participants,
27111
27147
  (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,
27112
27148
  (SELECT json_object('person', person, 'names', json(names)) FROM human_notify) AS notify,
27113
- (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}
27149
+ (SELECT json_object('status', t.status, 'id', t.id, 'text', t.reply_text, 'lastStep', json(t.last_step), 'activity', json(t.activity),
27150
+ 'trigger', ${triggerOf("t")}) FROM turns t WHERE t.session_id = s.id AND t.status IN ${OPEN_TURN_STATUSES}
27114
27151
  ORDER BY t.started_at DESC LIMIT 1) AS open_turn
27115
27152
  FROM sessions s LEFT JOIN session_folds f ON f.session_id = s.id WHERE s.id = ?`;
27116
27153
  function foldState(row) {
@@ -27134,7 +27171,7 @@ function fold(state, chunks, now = Date.now()) {
27134
27171
  const { session } = state;
27135
27172
  const stmts = [], touched = /* @__PURE__ */ new Map();
27136
27173
  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));
27137
- let open3 = state.open && { ...state.open, lastStep: state.open.lastStep ?? null, status: state.open.status, endedAt: null, error: null };
27174
+ let open3 = state.open && { ...state.open, trigger: state.open.trigger ?? null, lastStep: state.open.lastStep ?? null, status: state.open.status, endedAt: null, error: null };
27138
27175
  const deliver = [];
27139
27176
  let last = state.foldedIndex;
27140
27177
  let at = now;
@@ -27183,15 +27220,17 @@ function fold(state, chunks, now = Date.now()) {
27183
27220
  if (!data.turnId) throw new Error("turn.started must name eve turnId");
27184
27221
  flush();
27185
27222
  end("cancelled", "superseded by a new turn");
27186
- open3 = { activity: initActivity(), id: turnId(session.id, index), text: "", lastStep: null, status: "streaming", endedAt: null, error: null };
27223
+ open3 = { activity: initActivity(), id: turnId(session.id, index), trigger: null, text: "", lastStep: null, status: "streaming", endedAt: null, error: null };
27187
27224
  touched.set(open3.id, open3);
27188
27225
  stmts.push(s(`INSERT OR IGNORE INTO turns (id, thread_id, session_id, eve_turn_id, bot, status, started_at)
27189
- VALUES (?, ?, ?, ?, ?, 'streaming', ?)`, open3.id, session.thread, session.id, data.turnId, session.bot, at));
27226
+ SELECT ?, ?, ?, ?, ?, 'streaming', ? WHERE EXISTS (SELECT 1 FROM sessions WHERE id = ? AND ended_at IS NULL)`, open3.id, session.thread, session.id, data.turnId, session.bot, at, session.id));
27190
27227
  log("turn.status", { turnId: open3.id, status: "streaming" });
27191
27228
  }
27192
27229
  if (!open3) continue;
27193
- for (const deliveryId2 of event.meta?.deliveryIds ?? [])
27230
+ for (const deliveryId2 of event.meta?.deliveryIds ?? []) {
27231
+ open3.trigger ??= messageOfDelivery(deliveryId2, session.bot);
27194
27232
  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));
27233
+ }
27195
27234
  if (event.meta?.deliveryIds?.length) touched.set(open3.id, open3);
27196
27235
  const activityEvent = event.type === "message.completed" ? { ...event, data: { ...data, message: data.message ?? open3.text } } : event;
27197
27236
  if (reduceActivity(open3.activity, activityEvent, at, cutOff(open3, event.type, data.turnId))) {
@@ -27252,9 +27291,10 @@ function fold(state, chunks, now = Date.now()) {
27252
27291
  const text = lastActWasOwnPost(open3.lastStep) ? "" : open3.text.trim();
27253
27292
  if (open3.status !== "cancelling") {
27254
27293
  const messageId2 = derivedMessageId(open3.id);
27255
- stmts.push(s(`INSERT OR IGNORE INTO messages (id, thread_id, author, author_kind, text, turn_id, created_at)
27256
- SELECT ?, ?, ?, 'agent', ?, ?, ? WHERE (? <> '' OR EXISTS (SELECT 1 FROM turn_attachments WHERE turn_id = ?))
27257
- 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, open3.id));
27294
+ const hop = hopAfter(session.id);
27295
+ stmts.push(s(`INSERT OR IGNORE INTO messages (id, thread_id, author, author_kind, text, turn_id, created_at, hop)
27296
+ SELECT ?, ?, ?, 'agent', ?, ?, ?, ${hop.sql} WHERE (? <> '' OR EXISTS (SELECT 1 FROM turn_attachments WHERE turn_id = ?))
27297
+ 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, ...hop.args, text, open3.id, open3.id));
27258
27298
  log("message.created", { messageId: messageId2, author: session.bot, authorKind: "agent" }, IF_INSERTED);
27259
27299
  stmts.push(...notificationFor(messageId2, "agent", text, state.notify ?? void 0));
27260
27300
  stmts.push(s(`INSERT OR IGNORE INTO message_attachments (message_id, position, upload_id, name, size)
@@ -27306,10 +27346,15 @@ function fold(state, chunks, now = Date.now()) {
27306
27346
  JSON.stringify(t.activity),
27307
27347
  t.id
27308
27348
  ));
27349
+ if (t.endedAt !== null) stmts.push(...failureStatements({ id: t.id, threadId: session.thread, bot: session.bot, trigger: t.trigger, status: t.status, error: t.error }, t.endedAt));
27350
+ }
27351
+ if (last > state.foldedIndex) {
27352
+ stmts.push(s(`INSERT INTO log (thread_id, type, data, at) SELECT t.thread_id, 'turn.status', json_object('turnId', t.id, 'status', t.status), ?
27353
+ FROM turns t, local_runner r WHERE t.session_id = ? AND r.id = 1 AND t.status IN ${OPEN_TURN_STATUSES} AND t.started_at < r.claimed_at AND ${onLocal("t.bot")}
27354
+ AND NOT EXISTS (SELECT 1 FROM session_folds f WHERE f.session_id = t.session_id AND f.folded_at >= r.claimed_at)`, now, session.id));
27355
+ stmts.push(s(`INSERT INTO session_folds (session_id, folded_index, folded_at) VALUES (?, ?, ?)
27356
+ ON CONFLICT (session_id) DO UPDATE SET folded_index = excluded.folded_index, folded_at = excluded.folded_at`, session.id, last, now));
27309
27357
  }
27310
- if (last > state.foldedIndex)
27311
- stmts.push(s(`INSERT INTO session_folds (session_id, folded_index) VALUES (?, ?)
27312
- ON CONFLICT (session_id) DO UPDATE SET folded_index = excluded.folded_index`, session.id, last));
27313
27358
  return { statements: stmts, deliver };
27314
27359
  }
27315
27360
  async function foldInTx(tx, sessionId2, chunks) {