bazilion 0.11.0 → 0.12.0

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.
Files changed (69) hide show
  1. package/dist/cli.js +433 -146
  2. package/dist/cli.js.map +1 -1
  3. package/dist/daemon.js +1382 -232
  4. package/dist/daemon.js.map +1 -1
  5. package/dist/migrations/0001_init.sql +81 -0
  6. package/dist/web/client/assets/AgentTabs-R6gSjIj3.js +1 -0
  7. package/dist/web/client/assets/{ProductionTeamPolicyEditor-B6dbpuYy.js → ProductionTeamPolicyEditor-CSL3W7XM.js} +2 -2
  8. package/dist/web/client/assets/{ProfileCommunicationEditor-Bf8UvmZT.js → ProfileCommunicationEditor-CwU0QkmE.js} +1 -1
  9. package/dist/web/client/assets/{TeamTabs-sQBHChtd.js → TeamTabs-C1Hakkff.js} +1 -1
  10. package/dist/web/client/assets/{TemplatesTabs-DPsuwsWB.js → TemplatesTabs-klEydJ8-.js} +1 -1
  11. package/dist/web/client/assets/{_id-C9rkCclz.js → _id-BOC7ioJu.js} +1 -1
  12. package/dist/web/client/assets/{_id-DYASsGQ8.js → _id-CnVq-rED.js} +1 -1
  13. package/dist/web/client/assets/{_id-DE4IY5bL.js → _id-e4u7dmBb.js} +1 -1
  14. package/dist/web/client/assets/{_id-D65IzC8H.js → _id-wkjjWLz0.js} +1 -1
  15. package/dist/web/client/assets/{activity-C7o6-xHg.js → activity-uqhwzmiy.js} +1 -1
  16. package/dist/web/client/assets/{agents-UroO4d25.js → agents-B2usE-L_.js} +1 -1
  17. package/dist/web/client/assets/{agents-h8yWXQ2H.js → agents-FaQtkAWs.js} +1 -1
  18. package/dist/web/client/assets/{approvals-DKhF0YLC.js → approvals-DqLE6ttw.js} +1 -1
  19. package/dist/web/client/assets/attention-CkGozYnJ.js +1 -0
  20. package/dist/web/client/assets/attention-Gavt2yEp.js +1 -0
  21. package/dist/web/client/assets/chevron-down-CJk0zGNT.js +1 -0
  22. package/dist/web/client/assets/{config-q8JDresK.js → config-xrXoDEWM.js} +1 -1
  23. package/dist/web/client/assets/{context-CBucAoi6.js → context-ZWqx3uiC.js} +1 -1
  24. package/dist/web/client/assets/inbox-BoIvl9ZF.js +1 -0
  25. package/dist/web/client/assets/{index-DUXAevpZ.js → index-nb2MV8X1.js} +3 -3
  26. package/dist/web/client/assets/learning-6_oYn1l7.js +1 -0
  27. package/dist/web/client/assets/{login-CNAV9Wer.js → login-DM1ytxQL.js} +1 -1
  28. package/dist/web/client/assets/{mcp-BKdEFwHO.js → mcp-CetZbyUh.js} +1 -1
  29. package/dist/web/client/assets/{members-BDqZQh26.js → members-DdqWtwZd.js} +1 -1
  30. package/dist/web/client/assets/{memory-DVdEVr6L.js → memory-FNrxFHmC.js} +1 -1
  31. package/dist/web/client/assets/plus-C3GroITC.js +1 -0
  32. package/dist/web/client/assets/{policy-DdDctqvg.js → policy-BKPejYRI.js} +1 -1
  33. package/dist/web/client/assets/{routes-BEfALSZv.js → routes-CH1ZTTo-.js} +1 -1
  34. package/dist/web/client/assets/{services-DZP2ybzb.js → services-zk9SqN06.js} +1 -1
  35. package/dist/web/client/assets/{skills-DoeIjITs.js → skills-tXDpomHW.js} +1 -1
  36. package/dist/web/client/assets/styles-DMltY3su.css +2 -0
  37. package/dist/web/client/assets/{teams-W3j-UoJ9.js → teams-BZYjekBN.js} +1 -1
  38. package/dist/web/client/assets/{teams-DlHRnrQw.js → teams-D1c8zlxs.js} +1 -1
  39. package/dist/web/client/assets/{telegram-2OdeGv4E.js → telegram-DCe1CWMt.js} +1 -1
  40. package/dist/web/client/assets/{tokens-MuBfmgue.js → tokens-DAPjRS0N.js} +1 -1
  41. package/dist/web/client/assets/triggers-D8R-5guA.js +1 -0
  42. package/dist/web/client/assets/{welcome-CvNr5IMi.js → welcome-CuK0shDJ.js} +1 -1
  43. package/dist/web/server/assets/{AgentTabs-DoMDJBXH.js → AgentTabs-aXdsiiMp.js} +5 -0
  44. package/dist/web/server/assets/{_id-Cg7x6q3g.js → _id-BHYLL8M-.js} +2 -2
  45. package/dist/web/server/assets/{_id-DqON-59C.js → _id-FcHdSocl.js} +1 -1
  46. package/dist/web/server/assets/{_tanstack-start-manifest_v-BXauUXg6.js → _tanstack-start-manifest_v-fLsYERQ-.js} +76 -53
  47. package/dist/web/server/assets/attention-BW_6ddkj.js +11 -0
  48. package/dist/web/server/assets/attention-BxkTf337.js +14 -0
  49. package/dist/web/server/assets/attention-CM-Ks1N9.js +19 -0
  50. package/dist/web/server/assets/attention-w21GQIGH.js +264 -0
  51. package/dist/web/server/assets/{inbox-CUIyf9MT.js → inbox-BLUzA5sQ.js} +40 -3
  52. package/dist/web/server/assets/{inbox-B3-TRsNI.js → inbox-CzlKT9Wg.js} +5 -3
  53. package/dist/web/server/assets/{inbox-BardsGyy.js → inbox-ZvDNAjaM.js} +1 -1
  54. package/dist/web/server/assets/learning-8zav4pIT.js +33 -0
  55. package/dist/web/server/assets/learning-B7PBkvER.js +16 -0
  56. package/dist/web/server/assets/learning-Cq7K2heb.js +312 -0
  57. package/dist/web/server/assets/{router-C5_NjTsP.js → router-CQuj7553.js} +80 -47
  58. package/dist/web/server/assets/{triggers-CBl4yMyw.js → triggers-BZPl5CHs.js} +1 -1
  59. package/dist/web/server/assets/{triggers-_3rdhwSN.js → triggers-CSNNzRIi.js} +2 -2
  60. package/dist/web/server/server.js +11 -3
  61. package/dist/worker.js +166 -36
  62. package/dist/worker.js.map +1 -1
  63. package/package.json +3 -3
  64. package/dist/web/client/assets/AgentTabs-B3hq5a3e.js +0 -1
  65. package/dist/web/client/assets/chevron-down-BK6NPMMP.js +0 -1
  66. package/dist/web/client/assets/inbox-BwoRikj1.js +0 -1
  67. package/dist/web/client/assets/plus-b4yFXP0O.js +0 -1
  68. package/dist/web/client/assets/styles-BYM0UGdF.css +0 -2
  69. package/dist/web/client/assets/triggers-CX7RMQFQ.js +0 -1
package/dist/daemon.js CHANGED
@@ -135,10 +135,10 @@ var require_isexe = __commonJS({
135
135
  if (typeof Promise !== "function") {
136
136
  throw new TypeError("callback not provided");
137
137
  }
138
- return new Promise(function(resolve7, reject) {
138
+ return new Promise(function(resolve7, reject2) {
139
139
  isexe(path, options || {}, function(er, is) {
140
140
  if (er) {
141
- reject(er);
141
+ reject2(er);
142
142
  } else {
143
143
  resolve7(is);
144
144
  }
@@ -207,16 +207,16 @@ var require_which = __commonJS({
207
207
  opt = {};
208
208
  const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
209
209
  const found = [];
210
- const step = (i) => new Promise((resolve7, reject) => {
210
+ const step = (i) => new Promise((resolve7, reject2) => {
211
211
  if (i === pathEnv.length)
212
- return opt.all && found.length ? resolve7(found) : reject(getNotFoundError(cmd));
212
+ return opt.all && found.length ? resolve7(found) : reject2(getNotFoundError(cmd));
213
213
  const ppRaw = pathEnv[i];
214
214
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
215
215
  const pCmd = path.join(pathPart, cmd);
216
216
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
217
217
  resolve7(subStep(p, i, 0));
218
218
  });
219
- const subStep = (p, i, ii) => new Promise((resolve7, reject) => {
219
+ const subStep = (p, i, ii) => new Promise((resolve7, reject2) => {
220
220
  if (ii === pathExt.length)
221
221
  return resolve7(step(i + 1));
222
222
  const ext = pathExt[ii];
@@ -7796,7 +7796,7 @@ var init_stdio2 = __esm({
7796
7796
  if (this._process) {
7797
7797
  throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
7798
7798
  }
7799
- return new Promise((resolve7, reject) => {
7799
+ return new Promise((resolve7, reject2) => {
7800
7800
  this._process = (0, import_cross_spawn.default)(this._serverParams.command, this._serverParams.args ?? [], {
7801
7801
  // merge default env with server env because mcp server needs some env vars
7802
7802
  env: {
@@ -7809,7 +7809,7 @@ var init_stdio2 = __esm({
7809
7809
  cwd: this._serverParams.cwd
7810
7810
  });
7811
7811
  this._process.on("error", (error2) => {
7812
- reject(error2);
7812
+ reject2(error2);
7813
7813
  this.onerror?.(error2);
7814
7814
  });
7815
7815
  this._process.on("spawn", () => {
@@ -9803,7 +9803,7 @@ var init_sse = __esm({
9803
9803
  }
9804
9804
  _startOrAuth() {
9805
9805
  const fetchImpl = this?._eventSourceInit?.fetch ?? this._fetch ?? fetch;
9806
- return new Promise((resolve7, reject) => {
9806
+ return new Promise((resolve7, reject2) => {
9807
9807
  this._eventSource = new EventSource(this._url.href, {
9808
9808
  ...this._eventSourceInit,
9809
9809
  fetch: async (url2, init) => {
@@ -9824,11 +9824,11 @@ var init_sse = __esm({
9824
9824
  this._abortController = new AbortController();
9825
9825
  this._eventSource.onerror = (event2) => {
9826
9826
  if (event2.code === 401 && this._authProvider) {
9827
- this._authThenStart().then(resolve7, reject);
9827
+ this._authThenStart().then(resolve7, reject2);
9828
9828
  return;
9829
9829
  }
9830
9830
  const error2 = new SseError(event2.code, event2.message, event2);
9831
- reject(error2);
9831
+ reject2(error2);
9832
9832
  this.onerror?.(error2);
9833
9833
  };
9834
9834
  this._eventSource.onopen = () => {
@@ -9841,7 +9841,7 @@ var init_sse = __esm({
9841
9841
  throw new Error(`Endpoint origin does not match connection origin: ${this._endpoint.origin}`);
9842
9842
  }
9843
9843
  } catch (error2) {
9844
- reject(error2);
9844
+ reject2(error2);
9845
9845
  this.onerror?.(error2);
9846
9846
  void this.close();
9847
9847
  return;
@@ -11033,9 +11033,9 @@ var init_protocol = __esm({
11033
11033
  */
11034
11034
  request(request2, resultSchema, options) {
11035
11035
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
11036
- return new Promise((resolve7, reject) => {
11036
+ return new Promise((resolve7, reject2) => {
11037
11037
  const earlyReject = (error2) => {
11038
- reject(error2);
11038
+ reject2(error2);
11039
11039
  };
11040
11040
  if (!this._transport) {
11041
11041
  earlyReject(new Error("Not connected"));
@@ -11084,7 +11084,7 @@ var init_protocol = __esm({
11084
11084
  }
11085
11085
  };
11086
11086
  }
11087
- const cancel = (reason) => {
11087
+ const cancel2 = (reason) => {
11088
11088
  this._responseHandlers.delete(messageId);
11089
11089
  this._progressHandlers.delete(messageId);
11090
11090
  this._cleanupTimeout(messageId);
@@ -11097,31 +11097,31 @@ var init_protocol = __esm({
11097
11097
  }
11098
11098
  }, { relatedRequestId, resumptionToken, onresumptiontoken }).catch((error3) => this._onerror(new Error(`Failed to send cancellation: ${error3}`)));
11099
11099
  const error2 = reason instanceof McpError ? reason : new McpError(ErrorCode.RequestTimeout, String(reason));
11100
- reject(error2);
11100
+ reject2(error2);
11101
11101
  };
11102
11102
  this._responseHandlers.set(messageId, (response) => {
11103
11103
  if (options?.signal?.aborted) {
11104
11104
  return;
11105
11105
  }
11106
11106
  if (response instanceof Error) {
11107
- return reject(response);
11107
+ return reject2(response);
11108
11108
  }
11109
11109
  try {
11110
11110
  const parseResult = safeParse3(resultSchema, response.result);
11111
11111
  if (!parseResult.success) {
11112
- reject(parseResult.error);
11112
+ reject2(parseResult.error);
11113
11113
  } else {
11114
11114
  resolve7(parseResult.data);
11115
11115
  }
11116
11116
  } catch (error2) {
11117
- reject(error2);
11117
+ reject2(error2);
11118
11118
  }
11119
11119
  });
11120
11120
  options?.signal?.addEventListener("abort", () => {
11121
- cancel(options?.signal?.reason);
11121
+ cancel2(options?.signal?.reason);
11122
11122
  });
11123
11123
  const timeout = options?.timeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC;
11124
- const timeoutHandler = () => cancel(McpError.fromError(ErrorCode.RequestTimeout, "Request timed out", { timeout }));
11124
+ const timeoutHandler = () => cancel2(McpError.fromError(ErrorCode.RequestTimeout, "Request timed out", { timeout }));
11125
11125
  this._setupTimeout(messageId, timeout, options?.maxTotalTimeout, timeoutHandler, options?.resetTimeoutOnProgress ?? false);
11126
11126
  const relatedTaskId = relatedTask?.taskId;
11127
11127
  if (relatedTaskId) {
@@ -11140,12 +11140,12 @@ var init_protocol = __esm({
11140
11140
  timestamp: Date.now()
11141
11141
  }).catch((error2) => {
11142
11142
  this._cleanupTimeout(messageId);
11143
- reject(error2);
11143
+ reject2(error2);
11144
11144
  });
11145
11145
  } else {
11146
11146
  this._transport.send(jsonrpcRequest, { relatedRequestId, resumptionToken, onresumptiontoken }).catch((error2) => {
11147
11147
  this._cleanupTimeout(messageId);
11148
- reject(error2);
11148
+ reject2(error2);
11149
11149
  });
11150
11150
  }
11151
11151
  });
@@ -11372,15 +11372,15 @@ var init_protocol = __esm({
11372
11372
  }
11373
11373
  } catch {
11374
11374
  }
11375
- return new Promise((resolve7, reject) => {
11375
+ return new Promise((resolve7, reject2) => {
11376
11376
  if (signal.aborted) {
11377
- reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
11377
+ reject2(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
11378
11378
  return;
11379
11379
  }
11380
11380
  const timeoutId = setTimeout(resolve7, interval);
11381
11381
  signal.addEventListener("abort", () => {
11382
11382
  clearTimeout(timeoutId);
11383
- reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
11383
+ reject2(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
11384
11384
  }, { once: true });
11385
11385
  });
11386
11386
  }
@@ -13655,8 +13655,8 @@ var require_resolve = __commonJS({
13655
13655
  }
13656
13656
  return count2;
13657
13657
  }
13658
- function getFullPath(resolver, id = "", normalize) {
13659
- if (normalize !== false)
13658
+ function getFullPath(resolver, id = "", normalize2) {
13659
+ if (normalize2 !== false)
13660
13660
  id = normalizeId(id);
13661
13661
  const p = resolver.parse(id);
13662
13662
  return _getFullPath(resolver, p);
@@ -15052,7 +15052,7 @@ var require_fast_uri = __commonJS({
15052
15052
  "use strict";
15053
15053
  var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
15054
15054
  var { SCHEMES, getSchemeHandler } = require_schemes();
15055
- function normalize(uri, options) {
15055
+ function normalize2(uri, options) {
15056
15056
  if (typeof uri === "string") {
15057
15057
  uri = /** @type {T} */
15058
15058
  normalizeString(uri, options);
@@ -15319,7 +15319,7 @@ var require_fast_uri = __commonJS({
15319
15319
  }
15320
15320
  var fastUri = {
15321
15321
  SCHEMES,
15322
- normalize,
15322
+ normalize: normalize2,
15323
15323
  resolve: resolve7,
15324
15324
  resolveComponent,
15325
15325
  equal,
@@ -19125,7 +19125,7 @@ var init_client2 = __esm({
19125
19125
  import { serve } from "@hono/node-server";
19126
19126
 
19127
19127
  // ../daemon/src/app.ts
19128
- import { Hono as Hono16 } from "hono";
19128
+ import { Hono as Hono17 } from "hono";
19129
19129
 
19130
19130
  // ../daemon/src/lib/middleware-auth.ts
19131
19131
  import { getCookie } from "hono/cookie";
@@ -19154,6 +19154,7 @@ __export(agents_exports, {
19154
19154
  setModelOverride: () => setModelOverride,
19155
19155
  setName: () => setName,
19156
19156
  setReasoningLevel: () => setReasoningLevel,
19157
+ setReviewConfig: () => setReviewConfig,
19157
19158
  setStatus: () => setStatus,
19158
19159
  setTelegramIconEmoji: () => setTelegramIconEmoji,
19159
19160
  setTelegramMirrorMode: () => setTelegramMirrorMode,
@@ -19167,6 +19168,11 @@ function toAgent(r) {
19167
19168
  name: r.name,
19168
19169
  modelOverride: r.model_override,
19169
19170
  reasoningLevel: r.reasoning_level,
19171
+ reviewEnabled: r.review_enabled === 1,
19172
+ reviewEveryNTurns: r.review_every_n_turns,
19173
+ reviewModel: r.review_model,
19174
+ reviewReasoningLevel: r.review_reasoning_level,
19175
+ reviewTurnsSinceLast: r.review_turns_since_last,
19170
19176
  status: r.status,
19171
19177
  dir: r.dir,
19172
19178
  teamId: r.team_id,
@@ -19201,6 +19207,11 @@ function insert(db, a) {
19201
19207
  telegramTopicId: null,
19202
19208
  telegramMirrorMode: mirrorMode,
19203
19209
  telegramIconEmoji: null,
19210
+ reviewEnabled: false,
19211
+ reviewEveryNTurns: 8,
19212
+ reviewModel: null,
19213
+ reviewReasoningLevel: "low",
19214
+ reviewTurnsSinceLast: 0,
19204
19215
  createdAt: now,
19205
19216
  archivedAt: null
19206
19217
  };
@@ -19211,6 +19222,13 @@ function setReasoningLevel(db, id, level) {
19211
19222
  function setModelOverride(db, id, model) {
19212
19223
  db.raw.run("UPDATE agents SET model_override = ? WHERE id = ?", [model, id]);
19213
19224
  }
19225
+ function setReviewConfig(db, id, config2) {
19226
+ db.raw.run(
19227
+ `UPDATE agents SET review_enabled = ?, review_every_n_turns = ?, review_model = ?,
19228
+ review_reasoning_level = ? WHERE id = ?`,
19229
+ [config2.enabled ? 1 : 0, config2.everyNTurns, config2.model, config2.reasoningLevel, id]
19230
+ );
19231
+ }
19214
19232
  function setName(db, id, name) {
19215
19233
  db.raw.run("UPDATE agents SET name = ? WHERE id = ?", [name, id]);
19216
19234
  }
@@ -20025,12 +20043,123 @@ function loadIdentityFromFile(path) {
20025
20043
  return identityHasValues(parsed) ? parsed : null;
20026
20044
  }
20027
20045
 
20046
+ // ../daemon/src/core/repos/agentLessonProposals.ts
20047
+ var agentLessonProposals_exports = {};
20048
+ __export(agentLessonProposals_exports, {
20049
+ approve: () => approve,
20050
+ get: () => get4,
20051
+ insert: () => insert3,
20052
+ listForAgent: () => listForAgent,
20053
+ listForReview: () => listForReview,
20054
+ reject: () => reject,
20055
+ revoke: () => revoke,
20056
+ updatePending: () => updatePending
20057
+ });
20058
+ import { randomUUID as randomUUID2 } from "crypto";
20059
+ function toProposal(row) {
20060
+ return {
20061
+ id: row.id,
20062
+ reviewId: row.review_id,
20063
+ agentId: row.agent_id,
20064
+ scope: row.scope,
20065
+ text: row.text,
20066
+ evidence: JSON.parse(row.evidence_json),
20067
+ status: row.status,
20068
+ version: row.version,
20069
+ decidedAt: row.decided_at,
20070
+ revokedAt: row.revoked_at,
20071
+ appliedKey: row.applied_key,
20072
+ createdAt: row.created_at,
20073
+ updatedAt: row.updated_at
20074
+ };
20075
+ }
20076
+ function insert3(db, input) {
20077
+ const now = input.now ?? Date.now();
20078
+ const id = randomUUID2();
20079
+ db.raw.run(
20080
+ `INSERT INTO agent_lesson_proposals
20081
+ (id, review_id, agent_id, scope, text, evidence_json, status, version, created_at, updated_at)
20082
+ VALUES (?, ?, ?, ?, ?, ?, 'pending', 1, ?, ?)`,
20083
+ [
20084
+ id,
20085
+ input.reviewId,
20086
+ input.agentId,
20087
+ input.scope,
20088
+ input.text.trim(),
20089
+ JSON.stringify(input.evidence),
20090
+ now,
20091
+ now
20092
+ ]
20093
+ );
20094
+ const proposal = get4(db, id);
20095
+ if (!proposal) throw new Error("lesson proposal insert failed");
20096
+ return proposal;
20097
+ }
20098
+ function get4(db, id) {
20099
+ const row = db.raw.query("SELECT * FROM agent_lesson_proposals WHERE id = ?").get(id);
20100
+ return row ? toProposal(row) : null;
20101
+ }
20102
+ function listForReview(db, reviewId) {
20103
+ return db.raw.query(
20104
+ "SELECT * FROM agent_lesson_proposals WHERE review_id = ? ORDER BY created_at ASC"
20105
+ ).all(reviewId).map(toProposal);
20106
+ }
20107
+ function listForAgent(db, agentId, options = {}) {
20108
+ const limit = options.limit ?? 100;
20109
+ const rows = options.status ? db.raw.query(
20110
+ `SELECT * FROM agent_lesson_proposals WHERE agent_id = ? AND status = ?
20111
+ ORDER BY created_at DESC LIMIT ?`
20112
+ ).all(agentId, options.status, limit) : db.raw.query(
20113
+ `SELECT * FROM agent_lesson_proposals WHERE agent_id = ?
20114
+ ORDER BY created_at DESC LIMIT ?`
20115
+ ).all(agentId, limit);
20116
+ return rows.map(toProposal);
20117
+ }
20118
+ function changedProposal(db, id, changes) {
20119
+ return changes === 1 ? get4(db, id) : null;
20120
+ }
20121
+ function updatePending(db, id, version2, input, now = Date.now()) {
20122
+ const result = db.raw.run(
20123
+ `UPDATE agent_lesson_proposals SET text = ?, scope = ?, version = version + 1, updated_at = ?
20124
+ WHERE id = ? AND version = ? AND status = 'pending'`,
20125
+ [input.text.trim(), input.scope, now, id, version2]
20126
+ );
20127
+ return changedProposal(db, id, Number(result.changes));
20128
+ }
20129
+ function approve(db, id, version2, appliedKey, now = Date.now()) {
20130
+ const result = db.raw.run(
20131
+ `UPDATE agent_lesson_proposals SET status = 'approved', applied_key = ?, decided_at = ?,
20132
+ version = version + 1, updated_at = ?
20133
+ WHERE id = ? AND version = ? AND status = 'pending'`,
20134
+ [appliedKey, now, now, id, version2]
20135
+ );
20136
+ return changedProposal(db, id, Number(result.changes));
20137
+ }
20138
+ function reject(db, id, version2, now = Date.now()) {
20139
+ const result = db.raw.run(
20140
+ `UPDATE agent_lesson_proposals SET status = 'rejected', decided_at = ?,
20141
+ version = version + 1, updated_at = ?
20142
+ WHERE id = ? AND version = ? AND status = 'pending'`,
20143
+ [now, now, id, version2]
20144
+ );
20145
+ return changedProposal(db, id, Number(result.changes));
20146
+ }
20147
+ function revoke(db, id, version2, now = Date.now()) {
20148
+ const result = db.raw.run(
20149
+ `UPDATE agent_lesson_proposals SET status = 'revoked', revoked_at = ?,
20150
+ version = version + 1, updated_at = ?
20151
+ WHERE id = ? AND version = ? AND status = 'approved'`,
20152
+ [now, now, id, version2]
20153
+ );
20154
+ return changedProposal(db, id, Number(result.changes));
20155
+ }
20156
+
20028
20157
  // ../daemon/src/core/repos/profiles.ts
20029
20158
  var profiles_exports = {};
20030
20159
  __export(profiles_exports, {
20031
- get: () => get4,
20160
+ get: () => get5,
20032
20161
  getDefaultSkills: () => getDefaultSkills,
20033
- insert: () => insert3,
20162
+ insert: () => insert4,
20034
20163
  list: () => list3,
20035
20164
  remove: () => remove3,
20036
20165
  setDefaultSkills: () => setDefaultSkills,
@@ -20047,7 +20176,7 @@ function toProfile(r) {
20047
20176
  updatedAt: r.updated_at
20048
20177
  };
20049
20178
  }
20050
- function insert3(db, p) {
20179
+ function insert4(db, p) {
20051
20180
  const now = Date.now();
20052
20181
  db.raw.run(
20053
20182
  `INSERT INTO profiles (id, name, dir, default_model, skills_mode, created_at, updated_at)
@@ -20056,7 +20185,7 @@ function insert3(db, p) {
20056
20185
  );
20057
20186
  return { ...p, createdAt: now, updatedAt: now };
20058
20187
  }
20059
- function get4(db, id) {
20188
+ function get5(db, id) {
20060
20189
  const row = db.raw.query("SELECT * FROM profiles WHERE id = ?").get(id);
20061
20190
  return row ? toProfile(row) : null;
20062
20191
  }
@@ -20095,7 +20224,7 @@ function resolveAgent(db, paths, agentId) {
20095
20224
  const agent = get(db, agentId);
20096
20225
  if (!agent) throw new Error(`agent not found: ${agentId}`);
20097
20226
  agent.identity = loadIdentityFromFile(join2(agent.dir, "IDENTITY.md"));
20098
- const profile = get4(db, agent.profileId);
20227
+ const profile = get5(db, agent.profileId);
20099
20228
  if (!profile) {
20100
20229
  throw new Error(`profile not found for agent ${agentId}: ${agent.profileId}`);
20101
20230
  }
@@ -20109,12 +20238,13 @@ function resolveAgent(db, paths, agentId) {
20109
20238
  model: agent.modelOverride ?? profile.defaultModel,
20110
20239
  reasoningLevel: agent.reasoningLevel,
20111
20240
  team,
20112
- skills: listAttachedSkills(db, agentId)
20241
+ skills: listAttachedSkills(db, agentId),
20242
+ privateLessons: listForAgent(db, agent.id, { status: "approved", limit: 100 }).filter((proposal) => proposal.scope === "private").map((proposal) => proposal.text)
20113
20243
  };
20114
20244
  }
20115
20245
 
20116
20246
  // ../daemon/src/core/agent/spawn.ts
20117
- import { randomUUID as randomUUID2 } from "crypto";
20247
+ import { randomUUID as randomUUID3 } from "crypto";
20118
20248
  import { mkdirSync as mkdirSync3, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
20119
20249
  import { join as join7 } from "path";
20120
20250
 
@@ -20125,10 +20255,10 @@ import { join as join3 } from "path";
20125
20255
  // ../daemon/src/core/repos/profileCommunicationDefaults.ts
20126
20256
  var profileCommunicationDefaults_exports = {};
20127
20257
  __export(profileCommunicationDefaults_exports, {
20128
- get: () => get5,
20258
+ get: () => get6,
20129
20259
  set: () => set
20130
20260
  });
20131
- function get5(db, profileId) {
20261
+ function get6(db, profileId) {
20132
20262
  const row = db.raw.query(
20133
20263
  `SELECT user_input, user_output, outside_team_input, outside_team_output, peer_default
20134
20264
  FROM profile_communication_defaults WHERE profile_id = ?`
@@ -20174,7 +20304,7 @@ function readOptional(path) {
20174
20304
  return existsSync2(path) ? readFileSync3(path, "utf8") : null;
20175
20305
  }
20176
20306
  function loadProfile(db, id) {
20177
- const profile = get4(db, id);
20307
+ const profile = get5(db, id);
20178
20308
  if (!profile) throw new Error(`profile not found: ${id}`);
20179
20309
  const soul = readFileSync3(join3(profile.dir, "SOUL.md"), "utf8");
20180
20310
  const identityRaw = readFileSync3(join3(profile.dir, "IDENTITY.md"), "utf8");
@@ -20194,7 +20324,7 @@ function loadProfile(db, id) {
20194
20324
  tools
20195
20325
  },
20196
20326
  identity: anyIdentity ? parsedIdentity : null,
20197
- communicationDefaults: get5(db, id)
20327
+ communicationDefaults: get6(db, id)
20198
20328
  };
20199
20329
  }
20200
20330
 
@@ -20376,7 +20506,7 @@ function createProfile(db, paths, input) {
20376
20506
  };
20377
20507
  writeFileSync2(join4(dir, "profile.json"), `${JSON.stringify(profileJson, null, 2)}
20378
20508
  `);
20379
- const profile = insert3(db, {
20509
+ const profile = insert4(db, {
20380
20510
  id: input.id,
20381
20511
  name: profileJson.name,
20382
20512
  dir,
@@ -20422,7 +20552,7 @@ function seedDefaults(db, paths, input) {
20422
20552
  team = registerTeam(db, { id: DEFAULT_TEAM_ID, name: "Default" }, paths);
20423
20553
  teamCreated = true;
20424
20554
  }
20425
- let profile = get4(db, DEFAULT_PROFILE_ID);
20555
+ let profile = get5(db, DEFAULT_PROFILE_ID);
20426
20556
  let profileCreated = false;
20427
20557
  if (!profile) {
20428
20558
  profile = createProfile(db, paths, {
@@ -20444,7 +20574,7 @@ function seedDefaults(db, paths, input) {
20444
20574
  }
20445
20575
  function ensureSetupSeeded(db, paths) {
20446
20576
  if (!isSetupComplete(db)) return null;
20447
- if (get4(db, DEFAULT_PROFILE_ID)) return null;
20577
+ if (get5(db, DEFAULT_PROFILE_ID)) return null;
20448
20578
  const first = listAvailableModels(db)[0];
20449
20579
  if (!first) return null;
20450
20580
  return seedDefaults(db, paths, { model: first.value });
@@ -20470,7 +20600,7 @@ function discoverSkills(paths) {
20470
20600
  // ../daemon/src/core/agent/spawn.ts
20471
20601
  function spawnAgent(db, paths, input) {
20472
20602
  const loaded = loadProfile(db, input.profileId);
20473
- const id = randomUUID2();
20603
+ const id = randomUUID3();
20474
20604
  const dir = paths.agentDir(id);
20475
20605
  try {
20476
20606
  return db.raw.transaction(() => {
@@ -20557,6 +20687,197 @@ function unarchiveAgent(db, id) {
20557
20687
  unarchive(db, agent.id);
20558
20688
  }
20559
20689
 
20690
+ // ../daemon/src/core/attention.ts
20691
+ var ATTENTION_KINDS = [
20692
+ "communication_approval",
20693
+ "lesson_proposal",
20694
+ "review_failure",
20695
+ "trigger_failure",
20696
+ "agent_loop_break"
20697
+ ];
20698
+ var bounded = (value, fallback) => (value?.trim() || fallback).replace(/(authorization:\s*bearer\s+)[^\s,;]+/gi, "$1[redacted]").replace(/\bsk-[a-z0-9_-]{8,}\b/gi, "[redacted]").replace(/(https?:\/\/)[^\s/@:]+:[^\s/@]+@/gi, "$1[redacted]@").slice(0, 500);
20699
+ var sources = [
20700
+ {
20701
+ kind: "communication_approval",
20702
+ query: (db) => db.raw.query(`SELECT ca.id sourceId, ca.created_at occurredAt, ca.updated_at updatedAt,
20703
+ COALESCE(ca.target_id, ca.source_id) agentId, a.name agentName,
20704
+ COALESCE(ca.target_team_id, ca.source_team_id) teamId, t.name teamName,
20705
+ NULL acknowledged_at
20706
+ FROM communication_approvals ca
20707
+ LEFT JOIN agents a ON a.id = COALESCE(NULLIF(ca.target_id,''), NULLIF(ca.source_id,''))
20708
+ LEFT JOIN teams t ON t.id = COALESCE(ca.target_team_id, ca.source_team_id)
20709
+ WHERE ca.status = 'pending' AND ca.expires_at > CAST(unixepoch('subsec') * 1000 AS INTEGER)`).all().map(
20710
+ (r) => ({
20711
+ ...r,
20712
+ kind: "communication_approval",
20713
+ severity: "action_required",
20714
+ title: "Communication approval required",
20715
+ diagnostic: "A policy-protected communication is waiting for an operator decision.",
20716
+ href: "/approvals",
20717
+ acknowledgeable: false
20718
+ })
20719
+ )
20720
+ },
20721
+ {
20722
+ kind: "lesson_proposal",
20723
+ query: (db) => db.raw.query(`SELECT p.id sourceId, p.created_at occurredAt, p.updated_at updatedAt,
20724
+ p.agent_id agentId, a.name agentName, a.team_id teamId, t.name teamName, NULL acknowledged_at
20725
+ FROM agent_lesson_proposals p JOIN agents a ON a.id = p.agent_id
20726
+ JOIN teams t ON t.id = a.team_id
20727
+ WHERE p.status = 'pending'`).all().map(
20728
+ (r) => ({
20729
+ ...r,
20730
+ kind: "lesson_proposal",
20731
+ severity: "action_required",
20732
+ title: "Lesson proposal ready",
20733
+ diagnostic: "A reviewed-learning proposal is waiting for approval or rejection.",
20734
+ href: `/agents/${encodeURIComponent(r.agentId ?? "")}/learning`,
20735
+ acknowledgeable: false
20736
+ })
20737
+ )
20738
+ },
20739
+ {
20740
+ kind: "review_failure",
20741
+ query: (db) => db.raw.query(`SELECT r.id sourceId, r.created_at occurredAt, r.updated_at updatedAt,
20742
+ r.agent_id agentId, a.name agentName, a.team_id teamId, t.name teamName,
20743
+ r.status, r.last_error diagnostic, aa.acknowledged_at FROM agent_reviews r
20744
+ JOIN agents a ON a.id = r.agent_id JOIN teams t ON t.id = a.team_id
20745
+ LEFT JOIN attention_acknowledgements aa ON aa.source_kind = 'review_failure' AND aa.source_id = r.id
20746
+ WHERE r.status IN ('failed','cancelled')`).all().map(
20747
+ (r) => ({
20748
+ ...r,
20749
+ kind: "review_failure",
20750
+ severity: "error",
20751
+ title: r.status === "cancelled" ? "Agent review cancelled" : "Agent review failed",
20752
+ diagnostic: bounded(r.diagnostic, "The review stopped without completing."),
20753
+ href: `/agents/${encodeURIComponent(r.agentId ?? "")}/learning`,
20754
+ acknowledgeable: true
20755
+ })
20756
+ )
20757
+ },
20758
+ {
20759
+ kind: "trigger_failure",
20760
+ query: (db) => db.raw.query(`SELECT d.id sourceId, d.scheduled_at occurredAt, d.updated_at updatedAt,
20761
+ d.agent_id agentId, a.name agentName, a.team_id teamId, t.name teamName,
20762
+ d.last_error diagnostic, aa.acknowledged_at FROM trigger_dispatches d
20763
+ JOIN agents a ON a.id = d.agent_id JOIN teams t ON t.id = a.team_id
20764
+ LEFT JOIN attention_acknowledgements aa ON aa.source_kind = 'trigger_failure' AND aa.source_id = d.id
20765
+ WHERE d.status = 'failed'`).all().map(
20766
+ (r) => ({
20767
+ ...r,
20768
+ kind: "trigger_failure",
20769
+ severity: "error",
20770
+ title: "Scheduled trigger failed",
20771
+ diagnostic: bounded(r.diagnostic, "The scheduled Agent turn exhausted its retries."),
20772
+ href: `/agents/${encodeURIComponent(r.agentId ?? "")}`,
20773
+ acknowledgeable: true
20774
+ })
20775
+ )
20776
+ },
20777
+ {
20778
+ kind: "agent_loop_break",
20779
+ query: (db) => db.raw.query(`SELECT e.id sourceId, e.created_at occurredAt, e.created_at updatedAt,
20780
+ e.from_agent_id agentId, a.name agentName, e.source_team_id teamId, t.name teamName,
20781
+ e.attempted_hop, e.max_hops, aa.acknowledged_at FROM agent_loop_break_events e
20782
+ JOIN agents a ON a.id = e.from_agent_id JOIN teams t ON t.id = e.source_team_id
20783
+ LEFT JOIN attention_acknowledgements aa ON aa.source_kind = 'agent_loop_break' AND aa.source_id = e.id`).all().map(
20784
+ (r) => ({
20785
+ ...r,
20786
+ kind: "agent_loop_break",
20787
+ severity: "warning",
20788
+ title: "Agent message loop stopped",
20789
+ diagnostic: `Message chain stopped at hop ${r.attempted_hop} (limit ${r.max_hops}).`,
20790
+ href: `/agents/${encodeURIComponent(r.agentId ?? "")}/inbox`,
20791
+ acknowledgeable: true
20792
+ })
20793
+ )
20794
+ }
20795
+ ];
20796
+ function normalize(row) {
20797
+ const { acknowledged_at, ...item } = row;
20798
+ return { ...item, key: `${item.kind}:${item.sourceId}`, acknowledgedAt: acknowledged_at };
20799
+ }
20800
+ function parseAttentionKey(key2) {
20801
+ const at = key2.indexOf(":");
20802
+ const kind = key2.slice(0, at);
20803
+ const sourceId = key2.slice(at + 1);
20804
+ return at > 0 && ATTENTION_KINDS.includes(kind) && sourceId ? { kind, sourceId } : null;
20805
+ }
20806
+ function projectAttention(db, options = {}) {
20807
+ const degraded = [];
20808
+ const items = sources.flatMap((source) => {
20809
+ if (options.kind && source.kind !== options.kind) return [];
20810
+ try {
20811
+ return source.query(db).map(normalize);
20812
+ } catch (error2) {
20813
+ degraded.push({
20814
+ kind: source.kind,
20815
+ error: bounded(error2 instanceof Error ? error2.message : String(error2), "Projection failed")
20816
+ });
20817
+ return [];
20818
+ }
20819
+ });
20820
+ const state3 = options.state ?? "open";
20821
+ return {
20822
+ items: items.filter(
20823
+ (item) => state3 === "all" || (state3 === "acknowledged" ? item.acknowledgedAt !== null : item.acknowledgedAt === null)
20824
+ ).sort((a, b2) => b2.updatedAt - a.updatedAt || a.key.localeCompare(b2.key)).slice(0, Math.max(options.limit ?? 100, 1)),
20825
+ degraded
20826
+ };
20827
+ }
20828
+ function attentionSummary(db) {
20829
+ const projected = projectAttention(db, { state: "open", limit: 1e5 });
20830
+ const bySeverity = { action_required: 0, error: 0, warning: 0 };
20831
+ const byKind = Object.fromEntries(ATTENTION_KINDS.map((kind) => [kind, 0]));
20832
+ for (const item of projected.items) {
20833
+ bySeverity[item.severity]++;
20834
+ byKind[item.kind]++;
20835
+ }
20836
+ return { openTotal: projected.items.length, bySeverity, byKind, degraded: projected.degraded };
20837
+ }
20838
+ function acknowledgeAttention(db, key2, acknowledged) {
20839
+ const parsed = parseAttentionKey(key2);
20840
+ if (!parsed) throw new Error("invalid_attention_key");
20841
+ const current = projectAttention(db, {
20842
+ state: "all",
20843
+ kind: parsed.kind,
20844
+ limit: 1e5
20845
+ }).items.find((item) => item.sourceId === parsed.sourceId);
20846
+ if (!current) throw new Error("attention_source_not_found");
20847
+ if (!current.acknowledgeable) throw new Error("attention_action_required");
20848
+ if (acknowledged)
20849
+ db.raw.run(
20850
+ `INSERT INTO attention_acknowledgements (source_kind, source_id, acknowledged_at) VALUES (?, ?, ?) ON CONFLICT(source_kind, source_id) DO NOTHING`,
20851
+ [parsed.kind, parsed.sourceId, Date.now()]
20852
+ );
20853
+ else
20854
+ db.raw.run("DELETE FROM attention_acknowledgements WHERE source_kind = ? AND source_id = ?", [
20855
+ parsed.kind,
20856
+ parsed.sourceId
20857
+ ]);
20858
+ const updated = projectAttention(db, {
20859
+ state: "all",
20860
+ kind: parsed.kind,
20861
+ limit: 1e5
20862
+ }).items.find((item) => item.sourceId === parsed.sourceId);
20863
+ if (!updated) throw new Error("attention_source_not_found");
20864
+ return updated;
20865
+ }
20866
+ function acknowledgeAllAttention(db) {
20867
+ return db.raw.transaction(() => {
20868
+ const items = projectAttention(db, { state: "open", limit: 1e5 }).items.filter(
20869
+ (item) => item.acknowledgeable
20870
+ );
20871
+ const now = Date.now();
20872
+ for (const item of items)
20873
+ db.raw.run(
20874
+ `INSERT INTO attention_acknowledgements (source_kind, source_id, acknowledged_at) VALUES (?, ?, ?) ON CONFLICT(source_kind, source_id) DO NOTHING`,
20875
+ [item.kind, item.sourceId, now]
20876
+ );
20877
+ return items.length;
20878
+ })();
20879
+ }
20880
+
20560
20881
  // ../daemon/src/core/db/client.ts
20561
20882
  import { backup, DatabaseSync } from "node:sqlite";
20562
20883
  function wrap(rawDb) {
@@ -20717,7 +21038,7 @@ __export(teamTemplates_exports, {
20717
21038
  detail: () => detail2,
20718
21039
  edges: () => edges2,
20719
21040
  findReferencingProfile: () => findReferencingProfile,
20720
- get: () => get6,
21041
+ get: () => get7,
20721
21042
  insertCanonical: () => insertCanonical,
20722
21043
  insertCanonicalDefinition: () => insertCanonicalDefinition,
20723
21044
  list: () => list5,
@@ -20728,7 +21049,7 @@ __export(teamTemplates_exports, {
20728
21049
  slots: () => slots,
20729
21050
  updateCanonicalMetadata: () => updateCanonicalMetadata
20730
21051
  });
20731
- import { randomUUID as randomUUID3 } from "crypto";
21052
+ import { randomUUID as randomUUID4 } from "crypto";
20732
21053
  function toTemplate(row) {
20733
21054
  return {
20734
21055
  id: row.id,
@@ -20764,7 +21085,7 @@ function toEdge(row) {
20764
21085
  posture: row.posture
20765
21086
  };
20766
21087
  }
20767
- function get6(db, id) {
21088
+ function get7(db, id) {
20768
21089
  const row = db.raw.query("SELECT * FROM team_templates WHERE id = ?").get(id);
20769
21090
  return row ? toTemplate(row) : null;
20770
21091
  }
@@ -20772,7 +21093,7 @@ function list5(db) {
20772
21093
  return db.raw.query("SELECT * FROM team_templates ORDER BY created_at ASC").all().map(toTemplate);
20773
21094
  }
20774
21095
  function detail2(db, id) {
20775
- const template = get6(db, id);
21096
+ const template = get7(db, id);
20776
21097
  if (!template) return null;
20777
21098
  const currentSnapshot = revision(db, id, template.currentRevision);
20778
21099
  if (!currentSnapshot)
@@ -20872,7 +21193,7 @@ function replaceCanonicalDefinition(db, templateId, input) {
20872
21193
  if (resolvedRefs.has(reference)) {
20873
21194
  throw new Error(`invalid_template_definition: duplicate slot reference ${reference}`);
20874
21195
  }
20875
- resolvedRefs.set(reference, value.slotId ?? randomUUID3());
21196
+ resolvedRefs.set(reference, value.slotId ?? randomUUID4());
20876
21197
  }
20877
21198
  const resolvedEdges = input.edges.map((edge) => ({
20878
21199
  ...edge,
@@ -20881,7 +21202,7 @@ function replaceCanonicalDefinition(db, templateId, input) {
20881
21202
  }));
20882
21203
  const resolvedSlots = input.slots.map((slot) => ({
20883
21204
  ...slot,
20884
- slotId: slot.slotId ?? (slot.clientKey ? resolvedRefs.get(slot.clientKey) : randomUUID3())
21205
+ slotId: slot.slotId ?? (slot.clientKey ? resolvedRefs.get(slot.clientKey) : randomUUID4())
20885
21206
  }));
20886
21207
  validateCanonicalDefinition(resolvedSlots, resolvedEdges);
20887
21208
  const now = Date.now();
@@ -20893,7 +21214,7 @@ function replaceCanonicalDefinition(db, templateId, input) {
20893
21214
  for (let position = 0; position < resolvedSlots.length; position++) {
20894
21215
  const value = resolvedSlots[position];
20895
21216
  if (!value) continue;
20896
- const slotId = value.slotId ?? randomUUID3();
21217
+ const slotId = value.slotId ?? randomUUID4();
20897
21218
  if (activeIds.has(slotId))
20898
21219
  throw new Error(`invalid_template_definition: duplicate slot ${slotId}`);
20899
21220
  activeIds.add(slotId);
@@ -20958,7 +21279,7 @@ function cloneCanonical(db, sourceId, input) {
20958
21279
  const snapshot = revision(db, sourceId, input.expectedRevision);
20959
21280
  if (!snapshot)
20960
21281
  throw new Error(`template_snapshot_missing: ${sourceId}@${input.expectedRevision}`);
20961
- const translated = new Map(snapshot.slots.map((slot) => [slot.slotId, randomUUID3()]));
21282
+ const translated = new Map(snapshot.slots.map((slot) => [slot.slotId, randomUUID4()]));
20962
21283
  return insertCanonicalDefinition(db, {
20963
21284
  id: input.id,
20964
21285
  name: input.name ?? `${source.name} copy`,
@@ -21044,7 +21365,7 @@ function revision(db, templateId, revisionNumber) {
21044
21365
  };
21045
21366
  }
21046
21367
  function snapshotCurrent(db, templateId, revisionNumber, now) {
21047
- const template = get6(db, templateId);
21368
+ const template = get7(db, templateId);
21048
21369
  if (!template) throw new Error(`team template not found: ${templateId}`);
21049
21370
  db.raw.run(
21050
21371
  `INSERT INTO team_template_revisions (template_id, revision, name, user_md, created_at)
@@ -21066,7 +21387,7 @@ function snapshotCurrent(db, templateId, revisionNumber, now) {
21066
21387
  );
21067
21388
  }
21068
21389
  function pruneRevision(db, templateId, revisionNumber) {
21069
- const template = get6(db, templateId);
21390
+ const template = get7(db, templateId);
21070
21391
  if (!template) throw new Error(`team template not found: ${templateId}`);
21071
21392
  if (template.currentRevision === revisionNumber) {
21072
21393
  throw new Error(`template_revision_in_use: revision ${revisionNumber} is current`);
@@ -21101,7 +21422,7 @@ function requireDetail2(db, id) {
21101
21422
  return found;
21102
21423
  }
21103
21424
  function requireMutable(db, id) {
21104
- const template = get6(db, id);
21425
+ const template = get7(db, id);
21105
21426
  if (!template) throw new Error(`team template not found: ${id}`);
21106
21427
  if (template.deletedAt !== null) throw new Error(`template_deleted: ${id}`);
21107
21428
  return template;
@@ -21170,7 +21491,7 @@ function insertTemplateEdge(db, templateId, edge) {
21170
21491
 
21171
21492
  // ../daemon/src/core/profile/delete.ts
21172
21493
  function deleteProfile(db, id) {
21173
- const profile = get4(db, id);
21494
+ const profile = get5(db, id);
21174
21495
  if (!profile) throw new Error(`profile not found: ${id}`);
21175
21496
  const agents = list(db, { includeArchived: true }).filter((a) => a.profileId === id);
21176
21497
  if (agents.length > 0) {
@@ -21196,7 +21517,7 @@ function deleteProfile(db, id) {
21196
21517
  import { writeFileSync as writeFileSync5 } from "fs";
21197
21518
  import { join as join10 } from "path";
21198
21519
  function updateProfile(db, paths, id, input) {
21199
- const existing = get4(db, id);
21520
+ const existing = get5(db, id);
21200
21521
  if (!existing) throw new Error(`profile not found: ${id}`);
21201
21522
  const nextSkillsMode = input.skillsMode ?? existing.skillsMode;
21202
21523
  const next = {
@@ -21223,14 +21544,197 @@ function updateProfile(db, paths, id, input) {
21223
21544
  `${JSON.stringify(profileJson, null, 2)}
21224
21545
  `
21225
21546
  );
21226
- const updated = get4(db, id);
21547
+ const updated = get5(db, id);
21227
21548
  if (!updated) throw new Error(`profile vanished after update: ${id}`);
21228
21549
  return {
21229
21550
  ...updated,
21230
- communicationDefaults: get5(db, id)
21551
+ communicationDefaults: get6(db, id)
21231
21552
  };
21232
21553
  }
21233
21554
 
21555
+ // ../daemon/src/core/repos/agentReviews.ts
21556
+ var agentReviews_exports = {};
21557
+ __export(agentReviews_exports, {
21558
+ cancel: () => cancel,
21559
+ claim: () => claim,
21560
+ complete: () => complete,
21561
+ defer: () => defer,
21562
+ enqueueManual: () => enqueueManual,
21563
+ fail: () => fail,
21564
+ get: () => get8,
21565
+ getLatestCompleted: () => getLatestCompleted,
21566
+ getOpenForAgent: () => getOpenForAgent,
21567
+ listClaimable: () => listClaimable,
21568
+ listForAgent: () => listForAgent2,
21569
+ recordSuccessfulUserTurn: () => recordSuccessfulUserTurn,
21570
+ setSource: () => setSource
21571
+ });
21572
+ import { randomUUID as randomUUID5 } from "crypto";
21573
+ function toReview(row) {
21574
+ return {
21575
+ id: row.id,
21576
+ agentId: row.agent_id,
21577
+ status: row.status,
21578
+ trigger: row.trigger_kind,
21579
+ sourceSessionId: row.source_session_id,
21580
+ sourceStartOrdinal: row.source_start_ordinal,
21581
+ sourceEndOrdinal: row.source_end_ordinal,
21582
+ inputCharacters: row.input_characters,
21583
+ turnsReviewed: row.turns_reviewed,
21584
+ proposalCount: row.proposal_count,
21585
+ attemptCount: row.attempt_count,
21586
+ nextAttemptAt: row.next_attempt_at,
21587
+ leaseExpiresAt: row.lease_expires_at,
21588
+ startedAt: row.started_at,
21589
+ finishedAt: row.finished_at,
21590
+ lastError: row.last_error,
21591
+ createdAt: row.created_at,
21592
+ updatedAt: row.updated_at
21593
+ };
21594
+ }
21595
+ function insert5(db, agentId, trigger, now) {
21596
+ const id = randomUUID5();
21597
+ db.raw.run(
21598
+ `INSERT INTO agent_reviews
21599
+ (id, agent_id, status, trigger_kind, input_characters, turns_reviewed,
21600
+ proposal_count, attempt_count, next_attempt_at, created_at, updated_at)
21601
+ VALUES (?, ?, 'pending', ?, 0, 0, 0, 0, ?, ?, ?)`,
21602
+ [id, agentId, trigger, now, now, now]
21603
+ );
21604
+ const review = get8(db, id);
21605
+ if (!review) throw new Error("agent review enqueue failed");
21606
+ return review;
21607
+ }
21608
+ function get8(db, id) {
21609
+ const row = db.raw.query("SELECT * FROM agent_reviews WHERE id = ?").get(id);
21610
+ return row ? toReview(row) : null;
21611
+ }
21612
+ function getOpenForAgent(db, agentId) {
21613
+ const row = db.raw.query(
21614
+ `SELECT * FROM agent_reviews WHERE agent_id = ?
21615
+ AND status IN ('pending','running','retrying') LIMIT 1`
21616
+ ).get(agentId);
21617
+ return row ? toReview(row) : null;
21618
+ }
21619
+ function enqueueManual(db, agentId, now = Date.now()) {
21620
+ return db.raw.transaction(
21621
+ () => getOpenForAgent(db, agentId) ?? insert5(db, agentId, "manual", now)
21622
+ )();
21623
+ }
21624
+ function recordSuccessfulUserTurn(db, agentId, now = Date.now()) {
21625
+ return db.raw.transaction(() => {
21626
+ const updated = db.raw.run(
21627
+ `UPDATE agents SET review_turns_since_last = review_turns_since_last + 1
21628
+ WHERE id = ? AND review_enabled = 1`,
21629
+ [agentId]
21630
+ );
21631
+ if (Number(updated.changes) !== 1) return null;
21632
+ const state3 = db.raw.query(
21633
+ `SELECT review_turns_since_last, review_every_n_turns FROM agents WHERE id = ?`
21634
+ ).get(agentId);
21635
+ if (!state3 || state3.review_turns_since_last < state3.review_every_n_turns) return null;
21636
+ const open = getOpenForAgent(db, agentId);
21637
+ if (open) return open;
21638
+ const review = insert5(db, agentId, "cadence", now);
21639
+ db.raw.run("UPDATE agents SET review_turns_since_last = 0 WHERE id = ?", [agentId]);
21640
+ return review;
21641
+ })();
21642
+ }
21643
+ function listClaimable(db, now = Date.now()) {
21644
+ return db.raw.query(
21645
+ `SELECT * FROM agent_reviews
21646
+ WHERE (status IN ('pending','retrying') AND next_attempt_at <= ?)
21647
+ OR (status = 'running' AND lease_expires_at <= ?)
21648
+ ORDER BY created_at ASC`
21649
+ ).all(now, now).map(toReview);
21650
+ }
21651
+ function claim(db, id, options = {}) {
21652
+ const now = options.now ?? Date.now();
21653
+ const leaseExpiresAt = now + (options.leaseMs ?? 5 * 6e4);
21654
+ return db.raw.transaction(() => {
21655
+ const result = db.raw.run(
21656
+ `UPDATE agent_reviews SET status = 'running', attempt_count = attempt_count + 1,
21657
+ lease_expires_at = ?, started_at = ?, finished_at = NULL, updated_at = ?
21658
+ WHERE id = ? AND (
21659
+ (status IN ('pending','retrying') AND next_attempt_at <= ?)
21660
+ OR (status = 'running' AND lease_expires_at <= ?)
21661
+ )`,
21662
+ [leaseExpiresAt, now, now, id, now, now]
21663
+ );
21664
+ return Number(result.changes) === 1 ? get8(db, id) : null;
21665
+ })();
21666
+ }
21667
+ function defer(db, id, nextAttemptAt = Date.now() + 1e3) {
21668
+ db.raw.run(
21669
+ `UPDATE agent_reviews SET status = 'pending',
21670
+ attempt_count = CASE WHEN attempt_count > 0 THEN attempt_count - 1 ELSE 0 END,
21671
+ next_attempt_at = ?, lease_expires_at = NULL, started_at = NULL, updated_at = ?
21672
+ WHERE id = ? AND status = 'running'`,
21673
+ [nextAttemptAt, Date.now(), id]
21674
+ );
21675
+ }
21676
+ function getLatestCompleted(db, agentId) {
21677
+ const row = db.raw.query(
21678
+ `SELECT * FROM agent_reviews WHERE agent_id = ? AND status = 'completed'
21679
+ ORDER BY finished_at DESC LIMIT 1`
21680
+ ).get(agentId);
21681
+ return row ? toReview(row) : null;
21682
+ }
21683
+ function setSource(db, id, source) {
21684
+ db.raw.run(
21685
+ `UPDATE agent_reviews SET source_session_id = ?, source_start_ordinal = ?,
21686
+ source_end_ordinal = ?, input_characters = ?, turns_reviewed = ?, updated_at = ?
21687
+ WHERE id = ? AND status = 'running'`,
21688
+ [
21689
+ source.sessionId,
21690
+ source.startOrdinal,
21691
+ source.endOrdinal,
21692
+ source.inputCharacters,
21693
+ source.turnsReviewed,
21694
+ Date.now(),
21695
+ id
21696
+ ]
21697
+ );
21698
+ }
21699
+ function complete(db, id, proposalCount, now = Date.now()) {
21700
+ db.raw.run(
21701
+ `UPDATE agent_reviews SET status = 'completed', proposal_count = ?, lease_expires_at = NULL,
21702
+ finished_at = ?, last_error = NULL, updated_at = ? WHERE id = ? AND status = 'running'`,
21703
+ [proposalCount, now, now, id]
21704
+ );
21705
+ }
21706
+ function cancel(db, id, reason, now = Date.now()) {
21707
+ db.raw.run(
21708
+ `UPDATE agent_reviews SET status = 'cancelled', lease_expires_at = NULL, finished_at = ?,
21709
+ last_error = ?, updated_at = ? WHERE id = ? AND status IN ('pending','running','retrying')`,
21710
+ [now, reason.slice(0, 2e3), now, id]
21711
+ );
21712
+ }
21713
+ function fail(db, id, error2, options = {}) {
21714
+ const now = options.now ?? Date.now();
21715
+ const current = get8(db, id);
21716
+ if (current?.status !== "running") return;
21717
+ const terminal = current.attemptCount >= (options.maxAttempts ?? 3);
21718
+ const retryDelay = (options.retryDelayMs ?? 5e3) * 2 ** Math.max(0, current.attemptCount - 1);
21719
+ db.raw.run(
21720
+ `UPDATE agent_reviews SET status = ?, next_attempt_at = ?, lease_expires_at = NULL,
21721
+ finished_at = ?, last_error = ?, updated_at = ? WHERE id = ? AND status = 'running'`,
21722
+ [
21723
+ terminal ? "failed" : "retrying",
21724
+ terminal ? now : now + retryDelay,
21725
+ terminal ? now : null,
21726
+ error2.slice(0, 2e3),
21727
+ now,
21728
+ id
21729
+ ]
21730
+ );
21731
+ }
21732
+ function listForAgent2(db, agentId, limit = 50) {
21733
+ return db.raw.query(
21734
+ "SELECT * FROM agent_reviews WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?"
21735
+ ).all(agentId, limit).map(toReview);
21736
+ }
21737
+
21234
21738
  // ../daemon/src/core/repos/communicationApprovals.ts
21235
21739
  var communicationApprovals_exports = {};
21236
21740
  __export(communicationApprovals_exports, {
@@ -21238,13 +21742,13 @@ __export(communicationApprovals_exports, {
21238
21742
  decide: () => decide,
21239
21743
  expirePending: () => expirePending,
21240
21744
  finishDelivery: () => finishDelivery,
21241
- get: () => get7,
21745
+ get: () => get9,
21242
21746
  getByAttempt: () => getByAttempt,
21243
21747
  grantSchedulerTrigger: () => grantSchedulerTrigger,
21244
21748
  list: () => list6,
21245
21749
  request: () => request
21246
21750
  });
21247
- import { createHash, randomUUID as randomUUID4 } from "crypto";
21751
+ import { createHash, randomUUID as randomUUID6 } from "crypto";
21248
21752
  var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
21249
21753
  function endpoint(kind, id, teamId) {
21250
21754
  if (kind === "agent") return { kind, id };
@@ -21299,7 +21803,7 @@ function appendEvent(db, approvalId, name, actor, detail3 = null, now = Date.now
21299
21803
  `INSERT INTO communication_approval_events
21300
21804
  (id, approval_id, event, actor, detail, created_at)
21301
21805
  VALUES (?, ?, ?, ?, ?, ?)`,
21302
- [randomUUID4(), approvalId, name, actor, detail3, now]
21806
+ [randomUUID6(), approvalId, name, actor, detail3, now]
21303
21807
  );
21304
21808
  }
21305
21809
  function teams(db, input) {
@@ -21325,7 +21829,7 @@ function request(db, input, operation, authorization, payloadKind, payload, opti
21325
21829
  expireOne(db, existing, now);
21326
21830
  return toApproval(raw(db, existing.id));
21327
21831
  }
21328
- const id = randomUUID4();
21832
+ const id = randomUUID6();
21329
21833
  const [sourceGroup, targetGroup] = teams(db, input);
21330
21834
  const expiresAt = now + (options.ttlMs ?? DEFAULT_TTL_MS);
21331
21835
  db.raw.run(
@@ -21393,7 +21897,7 @@ function expirePending(db, now = Date.now()) {
21393
21897
  return rows.length;
21394
21898
  })();
21395
21899
  }
21396
- function get7(db, id, includePayload = false) {
21900
+ function get9(db, id, includePayload = false) {
21397
21901
  expirePending(db);
21398
21902
  const row = raw(db, id);
21399
21903
  if (!row) return null;
@@ -22019,6 +22523,13 @@ var SERVICES = [
22019
22523
  label: "Extra environment allowlist",
22020
22524
  placeholder: "MY_SAFE_VARIABLE,ANOTHER_SAFE_VARIABLE",
22021
22525
  description: "Optional comma-separated variable names copied into the container. Provider credentials remain excluded unless explicitly named."
22526
+ },
22527
+ {
22528
+ envVar: "BAZILION_AGENT_LOOP_MAX_HOPS",
22529
+ kind: "config",
22530
+ label: "Agent-message hop limit",
22531
+ placeholder: "8",
22532
+ description: "Maximum causal hops in an agent-to-agent message chain before the daemon stops it. Default 8."
22022
22533
  }
22023
22534
  ]
22024
22535
  },
@@ -22168,16 +22679,16 @@ function openConfig(db) {
22168
22679
  // ../daemon/src/core/repos/mcpServers.ts
22169
22680
  var mcpServers_exports = {};
22170
22681
  __export(mcpServers_exports, {
22171
- get: () => get8,
22682
+ get: () => get10,
22172
22683
  getByName: () => getByName,
22173
- insert: () => insert4,
22684
+ insert: () => insert6,
22174
22685
  list: () => list7,
22175
22686
  listEnabled: () => listEnabled2,
22176
22687
  remove: () => remove5,
22177
22688
  setEnabled: () => setEnabled2,
22178
22689
  update: () => update2
22179
22690
  });
22180
- import { randomUUID as randomUUID5 } from "crypto";
22691
+ import { randomUUID as randomUUID7 } from "crypto";
22181
22692
  function toServer(r) {
22182
22693
  let args = [];
22183
22694
  try {
@@ -22198,8 +22709,8 @@ function toServer(r) {
22198
22709
  updatedAt: r.updated_at
22199
22710
  };
22200
22711
  }
22201
- function insert4(db, input) {
22202
- const id = randomUUID5();
22712
+ function insert6(db, input) {
22713
+ const id = randomUUID7();
22203
22714
  const now = Date.now();
22204
22715
  db.raw.run(
22205
22716
  `INSERT INTO mcp_servers (id, name, transport, command, args, url, has_auth, enabled, created_at, updated_at)
@@ -22217,11 +22728,11 @@ function insert4(db, input) {
22217
22728
  now
22218
22729
  ]
22219
22730
  );
22220
- const row = get8(db, id);
22731
+ const row = get10(db, id);
22221
22732
  if (!row) throw new Error("mcp server insert failed");
22222
22733
  return row;
22223
22734
  }
22224
- function get8(db, id) {
22735
+ function get10(db, id) {
22225
22736
  const row = db.raw.query("SELECT * FROM mcp_servers WHERE id = ?").get(id);
22226
22737
  return row ? toServer(row) : null;
22227
22738
  }
@@ -22236,7 +22747,7 @@ function listEnabled2(db) {
22236
22747
  return db.raw.query("SELECT * FROM mcp_servers WHERE enabled = 1 ORDER BY created_at ASC").all().map(toServer);
22237
22748
  }
22238
22749
  function update2(db, id, input) {
22239
- const existing = get8(db, id);
22750
+ const existing = get10(db, id);
22240
22751
  if (!existing) return null;
22241
22752
  const next = {
22242
22753
  name: input.name ?? existing.name,
@@ -22263,7 +22774,7 @@ function update2(db, id, input) {
22263
22774
  id
22264
22775
  ]
22265
22776
  );
22266
- return get8(db, id);
22777
+ return get10(db, id);
22267
22778
  }
22268
22779
  function setEnabled2(db, id, enabled) {
22269
22780
  db.raw.run("UPDATE mcp_servers SET enabled = ?, updated_at = ? WHERE id = ?", [
@@ -22281,7 +22792,7 @@ var messages_exports = {};
22281
22792
  __export(messages_exports, {
22282
22793
  drainUnreadForAgent: () => drainUnreadForAgent,
22283
22794
  findReplies: () => findReplies,
22284
- get: () => get9,
22795
+ get: () => get11,
22285
22796
  listInbox: () => listInbox,
22286
22797
  listInboxForOperator: () => listInboxForOperator,
22287
22798
  listRecipientsWithUnread: () => listRecipientsWithUnread,
@@ -22291,37 +22802,44 @@ __export(messages_exports, {
22291
22802
  markRead: () => markRead,
22292
22803
  send: () => send
22293
22804
  });
22294
- import { randomUUID as randomUUID6 } from "crypto";
22805
+ import { randomUUID as randomUUID8 } from "crypto";
22295
22806
  function toMessage(r) {
22296
22807
  return {
22297
22808
  id: r.id,
22298
22809
  fromAgentId: r.from_agent_id,
22299
22810
  toAgentId: r.to_agent_id,
22300
22811
  replyTo: r.reply_to,
22812
+ causalChainId: r.causal_chain_id,
22813
+ causalHop: r.causal_hop,
22301
22814
  payload: r.payload,
22302
22815
  createdAt: r.created_at,
22303
22816
  readAt: r.read_at
22304
22817
  };
22305
22818
  }
22306
22819
  function send(db, input) {
22307
- const id = randomUUID6();
22820
+ const id = randomUUID8();
22308
22821
  const now = Date.now();
22822
+ const causalChainId = input.causalChainId ?? id;
22823
+ const causalHop = input.causalHop ?? 0;
22309
22824
  db.raw.run(
22310
- `INSERT INTO messages (id, from_agent_id, to_agent_id, reply_to, payload, created_at, read_at)
22311
- VALUES (?, ?, ?, ?, ?, ?, NULL)`,
22312
- [id, input.from, input.to, input.replyTo ?? null, input.payload, now]
22825
+ `INSERT INTO messages
22826
+ (id, from_agent_id, to_agent_id, reply_to, causal_chain_id, causal_hop, payload, created_at, read_at)
22827
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL)`,
22828
+ [id, input.from, input.to, input.replyTo ?? null, causalChainId, causalHop, input.payload, now]
22313
22829
  );
22314
22830
  return {
22315
22831
  id,
22316
22832
  fromAgentId: input.from,
22317
22833
  toAgentId: input.to,
22318
22834
  replyTo: input.replyTo ?? null,
22835
+ causalChainId,
22836
+ causalHop,
22319
22837
  payload: input.payload,
22320
22838
  createdAt: now,
22321
22839
  readAt: null
22322
22840
  };
22323
22841
  }
22324
- function get9(db, id) {
22842
+ function get11(db, id) {
22325
22843
  const row = db.raw.query("SELECT * FROM messages WHERE id = ?").get(id);
22326
22844
  return row ? toMessage(row) : null;
22327
22845
  }
@@ -22481,7 +22999,7 @@ function openSecrets(db, password) {
22481
22999
  // ../daemon/src/core/repos/skillMeta.ts
22482
23000
  var skillMeta_exports = {};
22483
23001
  __export(skillMeta_exports, {
22484
- get: () => get10,
23002
+ get: () => get12,
22485
23003
  listAll: () => listAll2,
22486
23004
  remove: () => remove6,
22487
23005
  upsert: () => upsert
@@ -22493,7 +23011,7 @@ function toMeta(r) {
22493
23011
  importedAt: r.imported_at
22494
23012
  };
22495
23013
  }
22496
- function get10(db, name) {
23014
+ function get12(db, name) {
22497
23015
  const row = db.raw.query("SELECT * FROM skill_meta WHERE name = ?").get(name);
22498
23016
  return row ? toMeta(row) : null;
22499
23017
  }
@@ -22501,7 +23019,7 @@ function listAll2(db) {
22501
23019
  return db.raw.query("SELECT * FROM skill_meta ORDER BY name ASC").all().map(toMeta);
22502
23020
  }
22503
23021
  function upsert(db, input) {
22504
- const existing = get10(db, input.name);
23022
+ const existing = get12(db, input.name);
22505
23023
  const source = input.source !== void 0 ? input.source : existing?.source ?? null;
22506
23024
  const importedAt = input.importedAt !== void 0 ? input.importedAt : existing?.importedAt ?? null;
22507
23025
  db.raw.run(
@@ -22521,7 +23039,7 @@ var telegram_acl_exports = {};
22521
23039
  __export(telegram_acl_exports, {
22522
23040
  add: () => add,
22523
23041
  count: () => count,
22524
- get: () => get11,
23042
+ get: () => get13,
22525
23043
  isAllowed: () => isAllowed,
22526
23044
  list: () => list8,
22527
23045
  ownerCount: () => ownerCount,
@@ -22539,12 +23057,12 @@ function toUser(r) {
22539
23057
  function list8(db) {
22540
23058
  return db.raw.query("SELECT * FROM telegram_allowed_users ORDER BY added_at ASC").all().map(toUser);
22541
23059
  }
22542
- function get11(db, userId) {
23060
+ function get13(db, userId) {
22543
23061
  const row = db.raw.query("SELECT * FROM telegram_allowed_users WHERE user_id = ?").get(userId);
22544
23062
  return row ? toUser(row) : null;
22545
23063
  }
22546
23064
  function isAllowed(db, userId) {
22547
- return get11(db, userId) !== null;
23065
+ return get13(db, userId) !== null;
22548
23066
  }
22549
23067
  function count(db) {
22550
23068
  return db.raw.query("SELECT COUNT(*) as c FROM telegram_allowed_users").get()?.c ?? 0;
@@ -22563,10 +23081,10 @@ function add(db, input) {
22563
23081
  ON CONFLICT(user_id) DO UPDATE SET username = excluded.username, label = excluded.label`,
22564
23082
  [input.userId, input.username ?? null, input.label ?? null, role, now]
22565
23083
  );
22566
- return get11(db, input.userId);
23084
+ return get13(db, input.userId);
22567
23085
  }
22568
23086
  function remove7(db, userId) {
22569
- const u = get11(db, userId);
23087
+ const u = get13(db, userId);
22570
23088
  if (!u) return false;
22571
23089
  if (u.role === "owner" && ownerCount(db) <= 1) return false;
22572
23090
  db.raw.run("DELETE FROM telegram_allowed_users WHERE user_id = ?", [userId]);
@@ -22578,17 +23096,17 @@ var triggerDispatches_exports = {};
22578
23096
  __export(triggerDispatches_exports, {
22579
23097
  cancelPendingForTrigger: () => cancelPendingForTrigger,
22580
23098
  cancelRunning: () => cancelRunning,
22581
- claim: () => claim,
22582
- defer: () => defer,
22583
- fail: () => fail,
22584
- get: () => get12,
23099
+ claim: () => claim2,
23100
+ defer: () => defer2,
23101
+ fail: () => fail2,
23102
+ get: () => get14,
22585
23103
  hasOpenForTrigger: () => hasOpenForTrigger,
22586
- listClaimable: () => listClaimable,
23104
+ listClaimable: () => listClaimable2,
22587
23105
  listForTrigger: () => listForTrigger,
22588
23106
  materialize: () => materialize,
22589
23107
  succeed: () => succeed
22590
23108
  });
22591
- import { randomUUID as randomUUID7 } from "crypto";
23109
+ import { randomUUID as randomUUID9 } from "crypto";
22592
23110
  function toDispatch(row) {
22593
23111
  return {
22594
23112
  id: row.id,
@@ -22614,7 +23132,7 @@ function materialize(db, input) {
22614
23132
  lease_expires_at, started_at, finished_at, last_error, created_at, updated_at)
22615
23133
  VALUES (?, ?, ?, ?, 'pending', 0, ?, NULL, NULL, NULL, NULL, ?, ?)
22616
23134
  ON CONFLICT(trigger_id, scheduled_at) DO NOTHING`,
22617
- [randomUUID7(), input.triggerId, input.agentId, input.scheduledAt, now, now, now]
23135
+ [randomUUID9(), input.triggerId, input.agentId, input.scheduledAt, now, now, now]
22618
23136
  );
22619
23137
  const row = db.raw.query(
22620
23138
  "SELECT * FROM trigger_dispatches WHERE trigger_id = ? AND scheduled_at = ?"
@@ -22622,11 +23140,11 @@ function materialize(db, input) {
22622
23140
  if (!row) throw new Error("trigger dispatch materialization failed");
22623
23141
  return toDispatch(row);
22624
23142
  }
22625
- function get12(db, id) {
23143
+ function get14(db, id) {
22626
23144
  const row = db.raw.query("SELECT * FROM trigger_dispatches WHERE id = ?").get(id);
22627
23145
  return row ? toDispatch(row) : null;
22628
23146
  }
22629
- function listClaimable(db, now = Date.now()) {
23147
+ function listClaimable2(db, now = Date.now()) {
22630
23148
  return db.raw.query(
22631
23149
  `SELECT * FROM trigger_dispatches
22632
23150
  WHERE (status IN ('pending','retrying') AND next_attempt_at <= ?)
@@ -22640,7 +23158,7 @@ function hasOpenForTrigger(db, triggerId) {
22640
23158
  WHERE trigger_id = ? AND status IN ('pending','running','retrying') LIMIT 1`
22641
23159
  ).get(triggerId) !== null;
22642
23160
  }
22643
- function claim(db, id, options = {}) {
23161
+ function claim2(db, id, options = {}) {
22644
23162
  const now = options.now ?? Date.now();
22645
23163
  const leaseExpiresAt = now + (options.leaseMs ?? 5 * 6e4);
22646
23164
  return db.raw.transaction(() => {
@@ -22666,7 +23184,7 @@ function succeed(db, id, now = Date.now()) {
22666
23184
  [now, now, id]
22667
23185
  );
22668
23186
  }
22669
- function defer(db, id, nextAttemptAt = Date.now() + 1e3) {
23187
+ function defer2(db, id, nextAttemptAt = Date.now() + 1e3) {
22670
23188
  db.raw.run(
22671
23189
  `UPDATE trigger_dispatches SET status = 'pending',
22672
23190
  attempt_count = CASE WHEN attempt_count > 0 THEN attempt_count - 1 ELSE 0 END,
@@ -22682,7 +23200,7 @@ function cancelRunning(db, id, reason, now = Date.now()) {
22682
23200
  [now, reason.slice(0, 2e3), now, id]
22683
23201
  );
22684
23202
  }
22685
- function fail(db, id, error2, options = {}) {
23203
+ function fail2(db, id, error2, options = {}) {
22686
23204
  const now = options.now ?? Date.now();
22687
23205
  const maxAttempts = options.maxAttempts ?? 3;
22688
23206
  const retryDelayMs = options.retryDelayMs ?? 5e3;
@@ -22720,15 +23238,15 @@ function listForTrigger(db, triggerId, limit = 20) {
22720
23238
  // ../daemon/src/core/repos/triggers.ts
22721
23239
  var triggers_exports = {};
22722
23240
  __export(triggers_exports, {
22723
- get: () => get13,
22724
- insert: () => insert5,
23241
+ get: () => get15,
23242
+ insert: () => insert7,
22725
23243
  listEnabled: () => listEnabled3,
22726
- listForAgent: () => listForAgent,
23244
+ listForAgent: () => listForAgent3,
22727
23245
  markFired: () => markFired,
22728
23246
  remove: () => remove8,
22729
23247
  setEnabled: () => setEnabled3
22730
23248
  });
22731
- import { randomUUID as randomUUID8 } from "crypto";
23249
+ import { randomUUID as randomUUID10 } from "crypto";
22732
23250
  function toTrigger(r) {
22733
23251
  return {
22734
23252
  id: r.id,
@@ -22742,8 +23260,8 @@ function toTrigger(r) {
22742
23260
  createdAt: r.created_at
22743
23261
  };
22744
23262
  }
22745
- function insert5(db, input) {
22746
- const id = randomUUID8();
23263
+ function insert7(db, input) {
23264
+ const id = randomUUID10();
22747
23265
  const now = Date.now();
22748
23266
  const enabled = input.enabled === false ? 0 : 1;
22749
23267
  db.raw.run(
@@ -22764,11 +23282,11 @@ function insert5(db, input) {
22764
23282
  createdAt: now
22765
23283
  };
22766
23284
  }
22767
- function get13(db, id) {
23285
+ function get15(db, id) {
22768
23286
  const row = db.raw.query("SELECT * FROM agent_triggers WHERE id = ?").get(id);
22769
23287
  return row ? toTrigger(row) : null;
22770
23288
  }
22771
- function listForAgent(db, agentId) {
23289
+ function listForAgent3(db, agentId) {
22772
23290
  return db.raw.query(
22773
23291
  "SELECT * FROM agent_triggers WHERE agent_id = ? ORDER BY created_at ASC"
22774
23292
  ).all(agentId).map(toTrigger);
@@ -22799,13 +23317,13 @@ var webTokens_exports = {};
22799
23317
  __export(webTokens_exports, {
22800
23318
  create: () => create,
22801
23319
  findActiveByToken: () => findActiveByToken,
22802
- get: () => get14,
23320
+ get: () => get16,
22803
23321
  hashToken: () => hashToken,
22804
23322
  list: () => list9,
22805
23323
  markUsed: () => markUsed,
22806
- revoke: () => revoke
23324
+ revoke: () => revoke2
22807
23325
  });
22808
- import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as randomUUID9 } from "crypto";
23326
+ import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as randomUUID11 } from "crypto";
22809
23327
  function toToken(r) {
22810
23328
  return {
22811
23329
  id: r.id,
@@ -22819,7 +23337,7 @@ function hashToken(token) {
22819
23337
  return createHash2("sha256").update(token).digest("hex");
22820
23338
  }
22821
23339
  function create(db, label) {
22822
- const id = randomUUID9();
23340
+ const id = randomUUID11();
22823
23341
  const token = randomBytes2(24).toString("hex");
22824
23342
  const tokenHash = hashToken(token);
22825
23343
  const now = Date.now();
@@ -22837,7 +23355,7 @@ function list9(db, opts) {
22837
23355
  const sql = opts?.includeRevoked ? "SELECT * FROM web_tokens ORDER BY created_at ASC" : "SELECT * FROM web_tokens WHERE revoked_at IS NULL ORDER BY created_at ASC";
22838
23356
  return db.raw.query(sql).all().map(toToken);
22839
23357
  }
22840
- function get14(db, id) {
23358
+ function get16(db, id) {
22841
23359
  const row = db.raw.query("SELECT * FROM web_tokens WHERE id = ?").get(id);
22842
23360
  return row ? toToken(row) : null;
22843
23361
  }
@@ -22851,7 +23369,7 @@ function findActiveByToken(db, token) {
22851
23369
  function markUsed(db, id, when = Date.now()) {
22852
23370
  db.raw.run("UPDATE web_tokens SET last_used_at = ? WHERE id = ?", [when, id]);
22853
23371
  }
22854
- function revoke(db, id, when = Date.now()) {
23372
+ function revoke2(db, id, when = Date.now()) {
22855
23373
  const res = db.raw.run(
22856
23374
  "UPDATE web_tokens SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL",
22857
23375
  [when, id]
@@ -23119,7 +23637,7 @@ function resolveAgentSkills(db, paths, agentId) {
23119
23637
  }
23120
23638
 
23121
23639
  // ../daemon/src/core/team/delete.ts
23122
- import { randomUUID as randomUUID10 } from "crypto";
23640
+ import { randomUUID as randomUUID12 } from "crypto";
23123
23641
  import { existsSync as existsSync9, renameSync as renameSync2, rmSync as rmSync6 } from "fs";
23124
23642
  function deleteTeam(db, paths, id, expectedTeamPolicyRevision) {
23125
23643
  const g2 = get3(db, id, paths);
@@ -23138,7 +23656,7 @@ function deleteTeam(db, paths, id, expectedTeamPolicyRevision) {
23138
23656
  `cannot delete team "${id}": ${members.length} agent(s) still belong to it: ${names}. Move or delete them first.`
23139
23657
  );
23140
23658
  }
23141
- const stagedPath = `${g2.path}.deleting-${randomUUID10()}`;
23659
+ const stagedPath = `${g2.path}.deleting-${randomUUID12()}`;
23142
23660
  const hadSlot = existsSync9(g2.path);
23143
23661
  if (hadSlot) renameSync2(g2.path, stagedPath);
23144
23662
  try {
@@ -23162,7 +23680,7 @@ function deleteTeam(db, paths, id, expectedTeamPolicyRevision) {
23162
23680
  }
23163
23681
 
23164
23682
  // ../daemon/src/core/team-policy/adopt.ts
23165
- import { randomUUID as randomUUID11 } from "crypto";
23683
+ import { randomUUID as randomUUID13 } from "crypto";
23166
23684
  function previewTeamPolicyAdoption(db, teamId, input) {
23167
23685
  return resolveAdoptionPlan(db, teamId, { ...input, previewEdges: [] }).resolved;
23168
23686
  }
@@ -23174,7 +23692,7 @@ function resolveAdoptionPlan(db, teamId, input) {
23174
23692
  `team_revision_conflict: expected ${input.teamExpectedRevision}, current ${teamPolicy.revision}`
23175
23693
  );
23176
23694
  }
23177
- const template = get6(db, input.templateId);
23695
+ const template = get7(db, input.templateId);
23178
23696
  if (!template) throw new Error(`team template not found: ${input.templateId}`);
23179
23697
  if (template.deletedAt !== null) throw new Error(`template_deleted: ${input.templateId}`);
23180
23698
  if (template.currentRevision !== input.templateExpectedRevision) {
@@ -23235,7 +23753,7 @@ function adoptTeamTemplate(db, teamId, input) {
23235
23753
  );
23236
23754
  db.raw.run("DELETE FROM template_instantiations WHERE team_id = ?", [teamId]);
23237
23755
  db.raw.run("DELETE FROM team_policy_edges WHERE team_id = ?", [teamId]);
23238
- const instantiationId = randomUUID11();
23756
+ const instantiationId = randomUUID13();
23239
23757
  db.raw.run(
23240
23758
  `INSERT INTO template_instantiations
23241
23759
  (id, team_id, template_id, template_revision, created_at)
@@ -23333,7 +23851,7 @@ function edgeKey(edge) {
23333
23851
  }
23334
23852
 
23335
23853
  // ../daemon/src/core/team-policy/authorization.ts
23336
- import { createHash as createHash3, randomUUID as randomUUID12 } from "crypto";
23854
+ import { createHash as createHash3, randomUUID as randomUUID14 } from "crypto";
23337
23855
  function denied(channel, code, reason) {
23338
23856
  return {
23339
23857
  decision: "deny",
@@ -23513,7 +24031,7 @@ function recordDenial(db, input, operation, result) {
23513
24031
  const sourceGroup = agentGroup(input.source) ?? agentGroup(input.target) ?? ("teamId" in input.source ? input.source.teamId : null);
23514
24032
  const targetGroup = agentGroup(input.target) ?? agentGroup(input.source) ?? ("teamId" in input.target ? input.target.teamId : null);
23515
24033
  const values = [
23516
- randomUUID12(),
24034
+ randomUUID14(),
23517
24035
  input.attemptKind,
23518
24036
  input.attemptId,
23519
24037
  operation,
@@ -23563,7 +24081,7 @@ function recordDenial(db, input, operation, result) {
23563
24081
  }
23564
24082
 
23565
24083
  // ../daemon/src/core/team-policy/source.ts
23566
- import { randomUUID as randomUUID13 } from "crypto";
24084
+ import { randomUUID as randomUUID15 } from "crypto";
23567
24085
  function diffTeamPolicy(db, teamId) {
23568
24086
  const detail3 = detail(db, teamId);
23569
24087
  if (!detail3) throw new Error(`team_policy_missing: ${teamId}`);
@@ -23625,13 +24143,13 @@ function saveTeamPolicyAsTemplate(db, paths, teamId, input) {
23625
24143
  `team_revision_conflict: expected ${input.expectedRevision}, current ${teamPolicy.revision}`
23626
24144
  );
23627
24145
  }
23628
- if (get6(db, input.id)) {
24146
+ if (get7(db, input.id)) {
23629
24147
  throw new Error(`team template already exists: ${input.id}`);
23630
24148
  }
23631
24149
  const team = get3(db, teamId, paths);
23632
24150
  if (!team) throw new Error(`team not found: ${teamId}`);
23633
24151
  const members = list(db, { includeArchived: true }).filter((agent) => agent.teamId === teamId);
23634
- const slotByAgent = new Map(members.map((agent) => [agent.id, randomUUID13()]));
24152
+ const slotByAgent = new Map(members.map((agent) => [agent.id, randomUUID15()]));
23635
24153
  const stateByAgent = new Map(
23636
24154
  agentState(db, teamId).map((state3) => [state3.agentId, state3])
23637
24155
  );
@@ -23673,7 +24191,7 @@ function updateTeamPolicySource(db, teamId, input) {
23673
24191
  if (!teamPolicy.baselineInstantiationId) throw new Error("baseline_missing: Team has no source");
23674
24192
  const baseline = instantiations(db, teamId).find((item) => item.id === teamPolicy.baselineInstantiationId);
23675
24193
  if (!baseline) throw new Error("baseline_missing: retained baseline does not exist");
23676
- const source = get6(db, baseline.templateId);
24194
+ const source = get7(db, baseline.templateId);
23677
24195
  if (!source) throw new Error(`team template not found: ${baseline.templateId}`);
23678
24196
  if (source.deletedAt !== null) throw new Error(`template_deleted: ${source.id}`);
23679
24197
  if (source.currentRevision !== baseline.templateRevision) {
@@ -23707,7 +24225,7 @@ function updateTeamPolicySource(db, teamId, input) {
23707
24225
  baselineBindings.map((binding) => [binding.agentId, binding.sourceSlotId])
23708
24226
  );
23709
24227
  for (const id of selectedIds) {
23710
- if (!slotByAgent.has(id)) slotByAgent.set(id, randomUUID13());
24228
+ if (!slotByAgent.has(id)) slotByAgent.set(id, randomUUID15());
23711
24229
  }
23712
24230
  const previousSlotById = new Map(previous.slots.map((slot) => [slot.slotId, slot]));
23713
24231
  const selectedMembers = [...selectedIds].map((id) => {
@@ -23775,7 +24293,7 @@ function pruneEmptyNonbaselineInstantiation(db, instantiationId, baselineId) {
23775
24293
  }
23776
24294
 
23777
24295
  // ../daemon/src/core/team-policy/spawn.ts
23778
- import { randomUUID as randomUUID14 } from "crypto";
24296
+ import { randomUUID as randomUUID16 } from "crypto";
23779
24297
  import { readdirSync as readdirSync4, rmSync as rmSync8 } from "fs";
23780
24298
 
23781
24299
  // ../daemon/src/core/team-policy/rm-with-retry.ts
@@ -23800,7 +24318,7 @@ async function rmWithRetry(target, opts = {}) {
23800
24318
 
23801
24319
  // ../daemon/src/core/team-policy/spawn.ts
23802
24320
  function previewTeamTemplateSpawn(db, paths, input) {
23803
- const source = get6(db, input.templateId);
24321
+ const source = get7(db, input.templateId);
23804
24322
  if (!source) throw new Error(`team template not found: ${input.templateId}`);
23805
24323
  if (source.deletedAt !== null) throw new Error(`template_deleted: ${input.templateId}`);
23806
24324
  if (source.currentRevision !== input.templateExpectedRevision) {
@@ -23866,7 +24384,7 @@ var SpawnTeamTemplateError = class extends Error {
23866
24384
  }
23867
24385
  };
23868
24386
  async function spawnTeamTemplate(db, paths, input) {
23869
- const source = get6(db, input.templateId);
24387
+ const source = get7(db, input.templateId);
23870
24388
  if (!source) throw new Error(`team template not found: ${input.templateId}`);
23871
24389
  if (source.deletedAt !== null) throw new Error(`template_deleted: ${input.templateId}`);
23872
24390
  if (source.currentRevision !== input.templateExpectedRevision) {
@@ -23878,7 +24396,7 @@ async function spawnTeamTemplate(db, paths, input) {
23878
24396
  if (!snapshot)
23879
24397
  throw new Error(`template_snapshot_missing: ${source.id}@${source.currentRevision}`);
23880
24398
  for (const slot of snapshot.slots) {
23881
- if (!get4(db, slot.profileId)) {
24399
+ if (!get5(db, slot.profileId)) {
23882
24400
  throw new Error(`profile not found: ${slot.profileId}`);
23883
24401
  }
23884
24402
  }
@@ -23891,7 +24409,7 @@ async function spawnTeamTemplate(db, paths, input) {
23891
24409
  const created = [];
23892
24410
  try {
23893
24411
  inTx(db, () => {
23894
- const transactionalSource = get6(db, input.templateId);
24412
+ const transactionalSource = get7(db, input.templateId);
23895
24413
  if (!transactionalSource || transactionalSource.deletedAt !== null) {
23896
24414
  throw new Error(`template_deleted: ${input.templateId}`);
23897
24415
  }
@@ -23942,7 +24460,7 @@ async function spawnTeamTemplate(db, paths, input) {
23942
24460
  })
23943
24461
  );
23944
24462
  }
23945
- const instantiationId = randomUUID14();
24463
+ const instantiationId = randomUUID16();
23946
24464
  db.raw.run(
23947
24465
  `INSERT INTO template_instantiations
23948
24466
  (id, team_id, template_id, template_revision, created_at)
@@ -24113,8 +24631,111 @@ async function runAgentLifecycleMutation(agentId, mutation) {
24113
24631
  }
24114
24632
  }
24115
24633
 
24634
+ // ../daemon/src/lib/agent-loop-guard.ts
24635
+ import { randomUUID as randomUUID17 } from "crypto";
24636
+ var DEFAULT_MAX_HOPS = 8;
24637
+ function toEvent(row) {
24638
+ return {
24639
+ id: row.id,
24640
+ causalChainId: row.causal_chain_id,
24641
+ parentMessageId: row.parent_message_id,
24642
+ fromAgentId: row.from_agent_id,
24643
+ toAgentId: row.to_agent_id,
24644
+ sourceTeamId: row.source_team_id,
24645
+ targetTeamId: row.target_team_id,
24646
+ attemptedHop: row.attempted_hop,
24647
+ maxHops: row.max_hops,
24648
+ reason: row.reason,
24649
+ origin: row.origin,
24650
+ createdAt: row.created_at
24651
+ };
24652
+ }
24653
+ function agentLoopMaxHops(db, env = process.env) {
24654
+ const raw2 = env.BAZILION_AGENT_LOOP_MAX_HOPS ?? (db ? openConfig(db).get("BAZILION_AGENT_LOOP_MAX_HOPS") : void 0);
24655
+ const parsed = Number(raw2);
24656
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : DEFAULT_MAX_HOPS;
24657
+ }
24658
+ var AgentLoopLimitError = class extends Error {
24659
+ constructor(event2) {
24660
+ super(
24661
+ `agent message chain ${event2.causalChainId} stopped at hop ${event2.attemptedHop} (limit ${event2.maxHops})`
24662
+ );
24663
+ this.event = event2;
24664
+ this.name = "AgentLoopLimitError";
24665
+ }
24666
+ event;
24667
+ };
24668
+ function resolveMessageCausality(db, input) {
24669
+ const parentMessageId = input.replyTo ?? input.causalParentMessageId ?? null;
24670
+ if (!parentMessageId) return { parentMessageId: null };
24671
+ const parent = messages_exports.get(db, parentMessageId);
24672
+ if (!parent) return { parentMessageId: null };
24673
+ return {
24674
+ causalChainId: parent.causalChainId,
24675
+ causalHop: parent.causalHop + 1,
24676
+ parentMessageId: parent.id
24677
+ };
24678
+ }
24679
+ function enforceMessageCausality(db, input) {
24680
+ const attemptedHop = input.causality.causalHop ?? 0;
24681
+ const maxHops = agentLoopMaxHops(db);
24682
+ if (attemptedHop <= maxHops) return null;
24683
+ const source = agents_exports.get(db, input.from);
24684
+ const target = agents_exports.get(db, input.to);
24685
+ if (!source || !target) return null;
24686
+ const event2 = {
24687
+ id: randomUUID17(),
24688
+ causalChainId: input.causality.causalChainId ?? input.causality.parentMessageId ?? randomUUID17(),
24689
+ parentMessageId: input.causality.parentMessageId,
24690
+ fromAgentId: input.from,
24691
+ toAgentId: input.to,
24692
+ sourceTeamId: source.teamId,
24693
+ targetTeamId: target.teamId,
24694
+ attemptedHop,
24695
+ maxHops,
24696
+ reason: "causal_hop_limit_exceeded",
24697
+ origin: input.origin,
24698
+ createdAt: Date.now()
24699
+ };
24700
+ db.raw.run(
24701
+ `INSERT INTO agent_loop_break_events
24702
+ (id, causal_chain_id, parent_message_id, from_agent_id, to_agent_id,
24703
+ source_team_id, target_team_id, attempted_hop, max_hops, reason, origin, created_at)
24704
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
24705
+ [
24706
+ event2.id,
24707
+ event2.causalChainId,
24708
+ event2.parentMessageId,
24709
+ event2.fromAgentId,
24710
+ event2.toAgentId,
24711
+ event2.sourceTeamId,
24712
+ event2.targetTeamId,
24713
+ event2.attemptedHop,
24714
+ event2.maxHops,
24715
+ event2.reason,
24716
+ event2.origin,
24717
+ event2.createdAt
24718
+ ]
24719
+ );
24720
+ return event2;
24721
+ }
24722
+ function listAgentLoopBreaks(db, agentId, limit = 50) {
24723
+ return db.raw.query(
24724
+ `SELECT * FROM agent_loop_break_events
24725
+ WHERE from_agent_id = ? OR to_agent_id = ?
24726
+ ORDER BY created_at DESC, id DESC LIMIT ?`
24727
+ ).all(agentId, agentId, Math.max(1, Math.min(limit, 100))).map(toEvent);
24728
+ }
24729
+ function selectCausalParent(messages) {
24730
+ let selected;
24731
+ for (const message of messages) {
24732
+ if (!selected || message.causalHop > selected.causalHop) selected = message;
24733
+ }
24734
+ return selected?.id ?? null;
24735
+ }
24736
+
24116
24737
  // ../daemon/src/lib/agent-turn.ts
24117
- import { randomUUID as randomUUID18 } from "crypto";
24738
+ import { randomUUID as randomUUID21 } from "crypto";
24118
24739
 
24119
24740
  // ../daemon/src/runtime/auth/openai-codex.ts
24120
24741
  import { loginOpenAICodex, refreshOpenAICodexToken } from "@earendil-works/pi-ai/oauth";
@@ -24896,9 +25517,9 @@ function isRetryableError(err) {
24896
25517
  return false;
24897
25518
  }
24898
25519
  function sleepWithAbort(ms, signal) {
24899
- return new Promise((resolve7, reject) => {
25520
+ return new Promise((resolve7, reject2) => {
24900
25521
  if (signal?.aborted) {
24901
- reject(new Error("aborted"));
25522
+ reject2(new Error("aborted"));
24902
25523
  return;
24903
25524
  }
24904
25525
  const t = setTimeout(() => {
@@ -24907,7 +25528,7 @@ function sleepWithAbort(ms, signal) {
24907
25528
  }, ms);
24908
25529
  const onAbort = () => {
24909
25530
  clearTimeout(t);
24910
- reject(new Error("aborted"));
25531
+ reject2(new Error("aborted"));
24911
25532
  };
24912
25533
  signal?.addEventListener("abort", onAbort, { once: true });
24913
25534
  });
@@ -25418,7 +26039,7 @@ var PROVIDERS = {
25418
26039
  function createProviderRegistry(config2, opts = {}) {
25419
26040
  const cache = /* @__PURE__ */ new Map();
25420
26041
  const enabledSet = opts.enabledSet;
25421
- function get15(name) {
26042
+ function get17(name) {
25422
26043
  const cached2 = cache.get(name);
25423
26044
  if (cached2) return cached2;
25424
26045
  const entry = PROVIDERS[name];
@@ -25450,7 +26071,7 @@ function createProviderRegistry(config2, opts = {}) {
25450
26071
  }
25451
26072
  const providerName = modelString.slice(0, idx);
25452
26073
  const model = modelString.slice(idx + 1);
25453
- return { provider: get15(providerName), model };
26074
+ return { provider: get17(providerName), model };
25454
26075
  },
25455
26076
  list() {
25456
26077
  return Object.entries(PROVIDERS).filter(([name, entry]) => {
@@ -25598,11 +26219,34 @@ ${agent.team.userMd.trim()}`
25598
26219
  This team's USER.md is empty. As you learn STABLE facts about the human (preferences, role, working hours, how they like to be addressed), populate it via \`user_md_get\` then \`user_md_write\` (always get first \u2014 you need the etag). Reserve this for things you're confident are durable \u2014 project knowledge belongs in \`memory_write\`, personal notes about yourself in \`home_write\` on IDENTITY.md. **Do NOT send peer messages announcing USER.md changes \u2014 every agent in the team sees the new content in their system prompt on their next turn automatically.**`
25599
26220
  );
25600
26221
  }
26222
+ const reviewedLessons = boundReviewedLessons(agent.privateLessons);
26223
+ if (reviewedLessons.length > 0) {
26224
+ parts.push(
26225
+ `# Reviewed lessons
26226
+
26227
+ These private lessons were explicitly approved by the human for your behavior. Follow them when relevant; current user instructions and higher-priority policy still win.
26228
+
26229
+ ${reviewedLessons.map((lesson) => `- ${lesson}`).join("\n")}`
26230
+ );
26231
+ }
25601
26232
  parts.push(
25602
26233
  "# Memory\n\nYou share a persistent memory backend with every other agent in this team. Use `memory_write` to remember things across sessions, and `memory_search` / `memory_read` / `memory_list` to recall them. This memory is for project knowledge \u2014 codebase notes, decisions, things the user told you about the work. For personal notes about yourself (preferences, persona quirks), use `home_write` on IDENTITY.md instead. Always check memory at the start of a session: another agent in the team may have already learned something useful. **Do NOT send peer messages announcing memory writes \u2014 every agent has access to the same store via `memory_search` and will find your note when they need it.**"
25603
26234
  );
25604
26235
  return parts.join("\n\n---\n\n");
25605
26236
  }
26237
+ function boundReviewedLessons(lessons, maxCharacters = 8e3) {
26238
+ const selected = [];
26239
+ let used = 0;
26240
+ for (const raw2 of lessons) {
26241
+ const lesson = raw2.trim();
26242
+ if (!lesson) continue;
26243
+ const remaining = maxCharacters - used;
26244
+ if (remaining <= 0) break;
26245
+ selected.push(lesson.slice(0, remaining));
26246
+ used += Math.min(lesson.length, remaining);
26247
+ }
26248
+ return selected;
26249
+ }
25606
26250
 
25607
26251
  // ../daemon/src/runtime/shell/tooling.ts
25608
26252
  import { existsSync as existsSync12, lstatSync as lstatSync2, realpathSync as realpathSync2 } from "fs";
@@ -25993,7 +26637,7 @@ function createApprovalGatedBashTool(base, host2) {
25993
26637
 
25994
26638
  // ../daemon/src/runtime/shell/docker.ts
25995
26639
  import { spawn } from "child_process";
25996
- import { randomUUID as randomUUID15 } from "crypto";
26640
+ import { randomUUID as randomUUID18 } from "crypto";
25997
26641
  import { closeSync as closeSync2, constants as constants2, mkdtempSync as mkdtempSync2, openSync as openSync2, rmSync as rmSync11, writeFileSync as writeFileSync8 } from "fs";
25998
26642
  import { access, realpath, stat } from "fs/promises";
25999
26643
  import { tmpdir as tmpdir2 } from "os";
@@ -26144,7 +26788,7 @@ function createDockerBashOperations(options) {
26144
26788
  const dockerClientEnv = await resolveLocalDockerClientEnv(dockerPath, hostEnv);
26145
26789
  const resolvedImage = await inspectSandboxImage(dockerPath, image, dockerClientEnv);
26146
26790
  if (signal?.aborted) throw new Error("aborted");
26147
- const containerName = `bazilion-bash-${process.pid}-${randomUUID15().replaceAll("-", "")}`;
26791
+ const containerName = `bazilion-bash-${process.pid}-${randomUUID18().replaceAll("-", "")}`;
26148
26792
  const spec = buildDockerRunSpec({
26149
26793
  image,
26150
26794
  env,
@@ -26175,7 +26819,7 @@ function executeDockerRun(spec, options) {
26175
26819
  )
26176
26820
  );
26177
26821
  }
26178
- return new Promise((resolve7, reject) => {
26822
+ return new Promise((resolve7, reject2) => {
26179
26823
  const child = spawn(spec.executable, spec.args, {
26180
26824
  stdio: ["ignore", "pipe", "pipe", envFileFd ?? "ignore"],
26181
26825
  windowsHide: true,
@@ -26199,23 +26843,23 @@ function executeDockerRun(spec, options) {
26199
26843
  if (!termination && options.signal?.aborted) termination = "aborted";
26200
26844
  if (termination) {
26201
26845
  await removeContainer(spec.executable, spec.containerName, spec.processEnv);
26202
- reject(
26846
+ reject2(
26203
26847
  termination === "aborted" ? new Error("aborted") : new Error(`timeout:${options.timeout}`)
26204
26848
  );
26205
26849
  return;
26206
26850
  }
26207
26851
  if (spawnError) {
26208
- reject(formatSpawnError(spec.executable, spawnError));
26852
+ reject2(formatSpawnError(spec.executable, spawnError));
26209
26853
  return;
26210
26854
  }
26211
26855
  if (exitCode === 125) {
26212
26856
  await removeContainer(spec.executable, spec.containerName, spec.processEnv);
26213
- reject(formatDockerRunError(spec, stderr));
26857
+ reject2(formatDockerRunError(spec, stderr));
26214
26858
  return;
26215
26859
  }
26216
26860
  if (exitCode === null) {
26217
26861
  await removeContainer(spec.executable, spec.containerName, spec.processEnv);
26218
- reject(new Error(`Docker sandbox CLI for container "${spec.containerName}" was killed`));
26862
+ reject2(new Error(`Docker sandbox CLI for container "${spec.containerName}" was killed`));
26219
26863
  return;
26220
26864
  }
26221
26865
  resolve7({ exitCode });
@@ -26387,7 +27031,7 @@ async function inspectSandboxImage(dockerPath, image, processEnv) {
26387
27031
  return id;
26388
27032
  }
26389
27033
  function runDockerInspection(dockerPath, args, processEnv, subject) {
26390
- return new Promise((resolve7, reject) => {
27034
+ return new Promise((resolve7, reject2) => {
26391
27035
  const child = spawn(dockerPath, args, {
26392
27036
  stdio: ["ignore", "pipe", "pipe"],
26393
27037
  windowsHide: true,
@@ -26401,7 +27045,7 @@ function runDockerInspection(dockerPath, args, processEnv, subject) {
26401
27045
  settled = true;
26402
27046
  clearTimeout(timeoutHandle);
26403
27047
  if (error2) {
26404
- reject(error2);
27048
+ reject2(error2);
26405
27049
  return;
26406
27050
  }
26407
27051
  resolve7(stdout.trim());
@@ -27918,7 +28562,8 @@ async function createBazilionSession(opts) {
27918
28562
  mcpTools,
27919
28563
  fileSink,
27920
28564
  bashApprovalHost,
27921
- refreshApiKey
28565
+ refreshApiKey,
28566
+ restricted
27922
28567
  } = opts;
27923
28568
  const { providerName, modelId } = splitModelString(agent.model);
27924
28569
  if (enabledProviders.size > 0 && !enabledProviders.has(providerName)) {
@@ -27950,9 +28595,9 @@ async function createBazilionSession(opts) {
27950
28595
  }
27951
28596
  const cwd = agent.team.path;
27952
28597
  if (!existsSync14(cwd)) mkdirSync6(cwd, { recursive: true });
27953
- const promptSkills = loadPromptSkills(paths.skillsDir, agent.skills);
28598
+ const promptSkills = restricted ? [] : loadPromptSkills(paths.skillsDir, agent.skills);
27954
28599
  const uploadsDir = join19(agent.agent.dir, "uploads");
27955
- const shellTools = createSessionShellTools(cwd, env, {
28600
+ const shellTools = restricted ? null : createSessionShellTools(cwd, env, {
27956
28601
  ...existsSync14(uploadsDir) ? { inputsDir: uploadsDir } : {},
27957
28602
  skillMounts: promptSkills.map((skill) => ({
27958
28603
  source: skill.hostDir,
@@ -27960,9 +28605,9 @@ async function createBazilionSession(opts) {
27960
28605
  })),
27961
28606
  approvalHost: bashApprovalHost
27962
28607
  });
27963
- const sessionDir = join19(paths.agentDir(agent.agent.id), "sessions");
28608
+ const sessionDir = restricted?.sessionDir ?? join19(paths.agentDir(agent.agent.id), "sessions");
27964
28609
  mkdirSync6(sessionDir, { recursive: true });
27965
- const existing = findMostRecent(sessionDir);
28610
+ const existing = restricted ? null : findMostRecent(sessionDir);
27966
28611
  const sessionManager = existing ? SessionManager.open(existing, sessionDir, cwd) : SessionManager.create(cwd, sessionDir);
27967
28612
  const settingsManager = SettingsManager.inMemory({
27968
28613
  compaction: { enabled: false },
@@ -27973,13 +28618,13 @@ async function createBazilionSession(opts) {
27973
28618
  provider: { maxRetryDelayMs: 8e3 }
27974
28619
  }
27975
28620
  });
27976
- const bazilionPrompt = buildSystemPrompt(agent, {
28621
+ const bazilionPrompt = restricted?.systemPrompt ?? buildSystemPrompt(agent, {
27977
28622
  skills: promptSkills,
27978
- sandboxMode: shellTools.config.sandboxMode
28623
+ sandboxMode: shellTools?.config.sandboxMode ?? "off"
27979
28624
  });
27980
28625
  const resourceLoader = createBazilionResourceLoader(bazilionPrompt);
27981
28626
  await resourceLoader.reload();
27982
- const bazilionTools = createBazilionCustomTools({
28627
+ const bazilionTools = restricted ? restricted.tools : createBazilionCustomTools({
27983
28628
  agent,
27984
28629
  memory,
27985
28630
  messagingHost,
@@ -27990,13 +28635,13 @@ async function createBazilionSession(opts) {
27990
28635
  fileSink,
27991
28636
  env
27992
28637
  });
27993
- const customTools = shellTools.customBash ? [...bazilionTools, shellTools.customBash] : bazilionTools;
27994
- const allowedTools = [...shellTools.hostToolNames, ...customTools.map((t) => t.name)];
28638
+ const customTools = shellTools?.customBash ? [...bazilionTools, shellTools.customBash] : bazilionTools;
28639
+ const allowedTools = [...shellTools?.hostToolNames ?? [], ...customTools.map((t) => t.name)];
27995
28640
  const { session } = await createAgentSession({
27996
28641
  cwd,
27997
28642
  agentDir: join19(paths.home, "pi"),
27998
28643
  model,
27999
- thinkingLevel: toPiThinkingLevel(agent.reasoningLevel),
28644
+ thinkingLevel: toPiThinkingLevel(restricted?.reasoningLevel ?? agent.reasoningLevel),
28000
28645
  tools: allowedTools,
28001
28646
  customTools,
28002
28647
  sessionManager,
@@ -28138,6 +28783,17 @@ function loadInitialMessages(agent, paths) {
28138
28783
  return [];
28139
28784
  }
28140
28785
  }
28786
+ function loadSessionMessages(agent, paths, sessionId) {
28787
+ if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) return [];
28788
+ const sessionDir = join19(paths.agentDir(agent.agent.id), "sessions");
28789
+ const file = join19(sessionDir, `${sessionId}.jsonl`);
28790
+ if (!existsSync14(file) || !existsSync14(agent.team.path)) return [];
28791
+ try {
28792
+ return SessionManager.open(file, sessionDir).buildSessionContext().messages;
28793
+ } catch {
28794
+ return [];
28795
+ }
28796
+ }
28141
28797
  function loadSessionHead(agent, paths) {
28142
28798
  const sessionDir = join19(paths.agentDir(agent.agent.id), "sessions");
28143
28799
  if (!existsSync14(sessionDir)) return { file: null, size: 0 };
@@ -28319,7 +28975,7 @@ function assertOpenAICodexProvider(requested, expected) {
28319
28975
  }
28320
28976
  function waitForRefresh(pending, signal) {
28321
28977
  if (!signal) return pending;
28322
- return new Promise((resolve7, reject) => {
28978
+ return new Promise((resolve7, reject2) => {
28323
28979
  let settled = false;
28324
28980
  const finish = (callback) => {
28325
28981
  if (settled) return;
@@ -28327,7 +28983,7 @@ function waitForRefresh(pending, signal) {
28327
28983
  signal.removeEventListener("abort", onAbort);
28328
28984
  callback();
28329
28985
  };
28330
- const onAbort = () => finish(() => reject(refreshCancelledError()));
28986
+ const onAbort = () => finish(() => reject2(refreshCancelledError()));
28331
28987
  signal.addEventListener("abort", onAbort, { once: true });
28332
28988
  if (signal.aborted) {
28333
28989
  onAbort();
@@ -28335,7 +28991,7 @@ function waitForRefresh(pending, signal) {
28335
28991
  }
28336
28992
  pending.then(
28337
28993
  (value) => finish(() => resolve7(value)),
28338
- (error2) => finish(() => reject(error2))
28994
+ (error2) => finish(() => reject2(error2))
28339
28995
  );
28340
28996
  });
28341
28997
  }
@@ -28366,6 +29022,14 @@ function tsxImportSpecifier() {
28366
29022
  cachedTsxImport = pathToFileURL(req.resolve("tsx")).href;
28367
29023
  return cachedTsxImport;
28368
29024
  }
29025
+ async function spawnReviewWorker(spec, opts = {}) {
29026
+ for await (const rawFrame of spawnWorkerTurn(spec, opts)) {
29027
+ const frame = rawFrame;
29028
+ if (frame.kind === "review_result") return frame.proposals;
29029
+ if (frame.kind === "fatal") throw new Error(frame.error);
29030
+ }
29031
+ throw new Error("review worker exited without a result");
29032
+ }
28369
29033
  async function* spawnWorkerTurn(spec, opts = {}) {
28370
29034
  const child = spawn2(process.execPath, workerSpawnArgs(opts.workerEntryPath), {
28371
29035
  env: opts.env ?? process.env,
@@ -28648,7 +29312,7 @@ async function resolveAgentApiKey(db, authToken, agent, opts = {}) {
28648
29312
  }
28649
29313
 
28650
29314
  // ../daemon/src/lib/attachments.ts
28651
- import { randomUUID as randomUUID16 } from "crypto";
29315
+ import { randomUUID as randomUUID19 } from "crypto";
28652
29316
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "fs";
28653
29317
  import { join as join20, posix } from "path";
28654
29318
  var MAX_FILE_BYTES = 25 * 1024 * 1024;
@@ -28673,7 +29337,7 @@ function saveInputFiles(agentDir, files, options = {}) {
28673
29337
  lines.push(`[attachment "${label}" skipped: too large (${fmtBytes(buf.byteLength)} > 25 MB)]`);
28674
29338
  continue;
28675
29339
  }
28676
- const filename = `${randomUUID16().slice(0, 8)}-${safeName(label)}`;
29340
+ const filename = `${randomUUID19().slice(0, 8)}-${safeName(label)}`;
28677
29341
  const path = join20(dir, filename);
28678
29342
  writeFileSync10(path, buf);
28679
29343
  const referencePath = options.referenceDir ? posix.join(options.referenceDir, filename) : path;
@@ -28959,10 +29623,10 @@ async function getSsrfProxy() {
28959
29623
  if (serverUrl) return serverUrl;
28960
29624
  const s = createServer(onRequest);
28961
29625
  s.on("connect", onConnect);
28962
- await new Promise((resolve7, reject) => {
28963
- s.once("error", reject);
29626
+ await new Promise((resolve7, reject2) => {
29627
+ s.once("error", reject2);
28964
29628
  s.listen(0, "127.0.0.1", () => {
28965
- s.off("error", reject);
29629
+ s.off("error", reject2);
28966
29630
  resolve7();
28967
29631
  });
28968
29632
  });
@@ -29358,7 +30022,7 @@ function createBrowserHost(config2) {
29358
30022
  }
29359
30023
 
29360
30024
  // ../daemon/src/lib/communication.ts
29361
- import { randomUUID as randomUUID17 } from "crypto";
30025
+ import { randomUUID as randomUUID20 } from "crypto";
29362
30026
 
29363
30027
  // ../daemon/src/lib/team-policy-contract.ts
29364
30028
  var TEAM_POLICY_MANAGEMENT_CONTRACT_VERSION = 1;
@@ -29503,11 +30167,26 @@ function isUserFacingFrame(frame) {
29503
30167
  return frame.event.type === "assistant_message" || frame.event.type === "assistant_delta" || frame.event.type === "file" || frame.event.type === "tool_result" && Boolean(frame.event.images?.length) || frame.event.type === "error";
29504
30168
  }
29505
30169
  function sendAgentMessage(db, input) {
29506
- if (!teamPolicyEnforcementEnabled()) return messages_exports.send(db, input);
29507
30170
  const attemptKind = input.attemptKind ?? "agent_tool";
29508
- const attemptId = input.attemptId ?? randomUUID17();
30171
+ const attemptId = input.attemptId ?? randomUUID20();
29509
30172
  const outcome = db.raw.transaction(
29510
30173
  () => {
30174
+ const causality = resolveMessageCausality(db, input);
30175
+ const loopBreak = enforceMessageCausality(db, {
30176
+ from: input.from,
30177
+ to: input.to,
30178
+ origin: input.origin,
30179
+ causality
30180
+ });
30181
+ if (loopBreak) return { loopBreak };
30182
+ const messageInput = {
30183
+ ...input,
30184
+ causalChainId: causality.causalChainId,
30185
+ causalHop: causality.causalHop
30186
+ };
30187
+ if (!teamPolicyEnforcementEnabled()) {
30188
+ return { message: messages_exports.send(db, messageInput) };
30189
+ }
29511
30190
  const authorization = {
29512
30191
  source: { kind: "agent", id: input.from },
29513
30192
  target: { kind: "agent", id: input.to },
@@ -29523,9 +30202,10 @@ function sendAgentMessage(db, input) {
29523
30202
  if (result.decision === "approval_required") {
29524
30203
  return { approval: { authorization: result, input: authorization } };
29525
30204
  }
29526
- return { message: messages_exports.send(db, input) };
30205
+ return { message: messages_exports.send(db, messageInput) };
29527
30206
  }
29528
30207
  )();
30208
+ if (outcome.loopBreak) throw new AgentLoopLimitError(outcome.loopBreak);
29529
30209
  if (outcome.denial) {
29530
30210
  observeDenial(outcome.denial, { origin: input.origin, attemptKind, attemptId });
29531
30211
  throw new CommunicationDeniedError(outcome.denial, attemptKind, attemptId);
@@ -29541,7 +30221,8 @@ function sendAgentMessage(db, input) {
29541
30221
  from: input.from,
29542
30222
  to: input.to,
29543
30223
  payload: input.payload,
29544
- replyTo: input.replyTo ?? null
30224
+ replyTo: input.replyTo ?? null,
30225
+ causalParentMessageId: input.causalParentMessageId ?? null
29545
30226
  },
29546
30227
  { requester: input.from }
29547
30228
  );
@@ -29982,17 +30663,21 @@ async function resolveMcpForTurn(db, env, authToken) {
29982
30663
  }
29983
30664
 
29984
30665
  // ../daemon/src/lib/messaging-host.ts
29985
- function createDbMessagingHost(db) {
30666
+ function createDbMessagingHost(db, opts = {}) {
29986
30667
  return {
29987
30668
  agentExists(agentId) {
29988
30669
  return agents_exports.get(db, agentId) !== null;
29989
30670
  },
29990
30671
  sendMessage(input) {
29991
- const m2 = sendAgentMessage(db, { ...input, origin: "agent_tool" });
30672
+ const m2 = sendAgentMessage(db, {
30673
+ ...input,
30674
+ causalParentMessageId: opts.causalParentMessageId,
30675
+ origin: "agent_tool"
30676
+ });
29992
30677
  return { messageId: m2.id };
29993
30678
  },
29994
- listInbox(agentId, opts) {
29995
- return deliverableInbox(db, agentId, opts?.unreadOnly === true);
30679
+ listInbox(agentId, opts2) {
30680
+ return deliverableInbox(db, agentId, opts2?.unreadOnly === true);
29996
30681
  },
29997
30682
  markRead(messageId) {
29998
30683
  messages_exports.markRead(db, messageId);
@@ -31965,7 +32650,7 @@ async function* runAgentTurn(agentId, rawMessage, opts = {}) {
31965
32650
  const authorization = opts.authorization ?? {
31966
32651
  origin: "internal_turn",
31967
32652
  attemptKind: "turn",
31968
- attemptId: randomUUID18()
32653
+ attemptId: randomUUID21()
31969
32654
  };
31970
32655
  const releaseLease = opts.acquiredLeaseRelease ?? await acquireAgentLifecycleLease(agentId);
31971
32656
  let agent;
@@ -32009,7 +32694,9 @@ async function* runAgentTurn(agentId, rawMessage, opts = {}) {
32009
32694
 
32010
32695
  ${fileNote}` : fileNote : rawMessage;
32011
32696
  const enabledProviders = Array.from(providerState_exports.listEnabled(db));
32012
- const messagingHost = createDbMessagingHost(db);
32697
+ const messagingHost = createDbMessagingHost(db, {
32698
+ causalParentMessageId: opts.causalParentMessageId
32699
+ });
32013
32700
  const userMdHost = createDbUserMdHost(db, paths);
32014
32701
  const { apiKey, refreshApiKey } = await resolveAgentApiKey(db, authToken, agent, {
32015
32702
  withRefresher: true
@@ -32019,6 +32706,7 @@ ${fileNote}` : fileNote : rawMessage;
32019
32706
  const mcp = await resolveMcpForTurn(db, env, authToken);
32020
32707
  mirrorTypingStart(agentId, `${authorization.attemptKind}:${authorization.attemptId}:typing`);
32021
32708
  let mirrorFrameIndex = 0;
32709
+ let completed = false;
32022
32710
  for await (const frame of spawnWorkerTurn(
32023
32711
  {
32024
32712
  agent,
@@ -32052,8 +32740,12 @@ ${fileNote}` : fileNote : rawMessage;
32052
32740
  e instanceof Error ? e.message : String(e)
32053
32741
  );
32054
32742
  });
32743
+ if (frame.kind === "done") completed = true;
32055
32744
  yield frame;
32056
32745
  }
32746
+ if (completed && (authorization.origin === "http_chat" || authorization.origin.startsWith("telegram"))) {
32747
+ agentReviews_exports.recordSuccessfulUserTurn(db, agentId);
32748
+ }
32057
32749
  } finally {
32058
32750
  mirrorTypingStop(agentId);
32059
32751
  unregisterAgent(agentId);
@@ -32131,6 +32823,194 @@ function validateCron(expr) {
32131
32823
  parseCron(expr);
32132
32824
  }
32133
32825
 
32826
+ // ../daemon/src/lib/review-digest.ts
32827
+ var SECRET_PATTERNS = [
32828
+ /\bBearer\s+[A-Za-z0-9._~+/-]+=*/gi,
32829
+ /\b(?:sk|pk)-[A-Za-z0-9_-]{12,}\b/g,
32830
+ /\b(?:api[_-]?key|token|password)\s*[:=]\s*[^\s,;]+/gi
32831
+ ];
32832
+ function redactReviewText(text2) {
32833
+ return SECRET_PATTERNS.reduce((value, pattern) => value.replace(pattern, "[REDACTED]"), text2);
32834
+ }
32835
+ function buildReviewDigest(input, options = {}) {
32836
+ const maxUserTurns = options.maxUserTurns ?? 8;
32837
+ const maxCharacters = options.maxCharacters ?? 4e4;
32838
+ const normalized = input.filter((entry) => !entry.sensitive && entry.ordinal >= 0 && entry.text.trim()).map((entry) => ({
32839
+ ...entry,
32840
+ text: entry.role === "tool" ? `[tool:${entry.toolName?.trim() || "unknown"}] completed` : redactReviewText(entry.text.trim())
32841
+ }));
32842
+ if (normalized.length === 0) return null;
32843
+ const selected = [];
32844
+ let userTurns = 0;
32845
+ let characters = 0;
32846
+ for (let index = normalized.length - 1; index >= 0; index -= 1) {
32847
+ const entry = normalized[index];
32848
+ if (!entry) continue;
32849
+ if (entry.role === "user" && userTurns >= maxUserTurns) break;
32850
+ const remaining = maxCharacters - characters;
32851
+ if (remaining <= 0) break;
32852
+ const text2 = entry.text.slice(Math.max(0, entry.text.length - remaining));
32853
+ if (!text2) break;
32854
+ selected.push({ ...entry, text: text2 });
32855
+ characters += text2.length;
32856
+ if (entry.role === "user") userTurns += 1;
32857
+ }
32858
+ selected.reverse();
32859
+ const firstUserIndex = selected.findIndex((entry) => entry.role === "user");
32860
+ const bounded2 = firstUserIndex >= 0 ? selected.slice(firstUserIndex) : selected;
32861
+ characters = bounded2.reduce((total, entry) => total + entry.text.length, 0);
32862
+ const first = bounded2[0];
32863
+ const last = bounded2.at(-1);
32864
+ if (!first || !last) return null;
32865
+ return {
32866
+ sessionId: last.sessionId,
32867
+ startOrdinal: first.ordinal,
32868
+ endOrdinal: last.ordinal,
32869
+ inputCharacters: characters,
32870
+ turnsReviewed: userTurns,
32871
+ entries: bounded2
32872
+ };
32873
+ }
32874
+
32875
+ // ../daemon/src/lib/review-dispatcher.ts
32876
+ function messageText(message) {
32877
+ if (message.role === "tool") return message.toolName ?? "unknown";
32878
+ return message.content;
32879
+ }
32880
+ function transcriptEntries(messages, sessionId) {
32881
+ return messages.flatMap((message, ordinal) => {
32882
+ if (message.role === "system") return [];
32883
+ return [
32884
+ {
32885
+ sessionId,
32886
+ ordinal,
32887
+ role: message.role,
32888
+ text: messageText(message),
32889
+ ...message.role === "tool" ? { toolName: message.toolName } : {}
32890
+ }
32891
+ ];
32892
+ });
32893
+ }
32894
+ function renderReviewInput(digest, privateLessons, sharedLessonKeys) {
32895
+ const entries = digest.entries.map(
32896
+ (entry) => `[${entry.sessionId}:${entry.ordinal}] ${entry.role.toUpperCase()}: ${entry.text}`
32897
+ ).join("\n\n");
32898
+ return `# Transcript digest
32899
+
32900
+ ${entries}
32901
+
32902
+ # Existing private lessons
32903
+ ${privateLessons.length ? privateLessons.map((text2) => `- ${text2}`).join("\n") : "(none)"}
32904
+
32905
+ # Existing shared lesson keys
32906
+ ${sharedLessonKeys.length ? sharedLessonKeys.map((key2) => `- ${key2}`).join("\n") : "(none)"}`;
32907
+ }
32908
+ async function dispatchAgentReview(reviewId) {
32909
+ const { db, paths, authToken } = getCtx();
32910
+ const claimed = agentReviews_exports.claim(db, reviewId);
32911
+ if (!claimed) return;
32912
+ if (isActiveAgent(claimed.agentId)) {
32913
+ agentReviews_exports.defer(db, claimed.id);
32914
+ return;
32915
+ }
32916
+ const releaseLease = await acquireAgentLifecycleLease(claimed.agentId);
32917
+ const controller = new AbortController();
32918
+ let registered = false;
32919
+ try {
32920
+ if (isActiveAgent(claimed.agentId)) {
32921
+ agentReviews_exports.defer(db, claimed.id);
32922
+ return;
32923
+ }
32924
+ registerAgent(claimed.agentId, controller);
32925
+ registered = true;
32926
+ } finally {
32927
+ releaseLease();
32928
+ }
32929
+ try {
32930
+ const resolved = resolveAgent(db, paths, claimed.agentId);
32931
+ const sessionHead = loadSessionHead(resolved, paths);
32932
+ const sessionId = sessionHead.file?.replace(/\.jsonl$/, "") ?? "";
32933
+ const previous = agentReviews_exports.getLatestCompleted(db, claimed.agentId);
32934
+ const messages = piMessagesToProviderView(loadInitialMessages(resolved, paths));
32935
+ const entries = transcriptEntries(messages, sessionId).filter(
32936
+ (entry) => previous?.sourceSessionId !== sessionId || previous.sourceEndOrdinal === null || entry.ordinal > previous.sourceEndOrdinal
32937
+ );
32938
+ const digest = buildReviewDigest(entries);
32939
+ if (!digest) {
32940
+ agentReviews_exports.complete(db, claimed.id, 0);
32941
+ return;
32942
+ }
32943
+ agentReviews_exports.setSource(db, claimed.id, digest);
32944
+ const privateLessons = agentLessonProposals_exports.listForAgent(db, claimed.agentId, { status: "approved" }).filter((proposal) => proposal.scope === "private").map((proposal) => proposal.text);
32945
+ const memory = qmdBackend(`${resolved.team.path}/memory`);
32946
+ await memory.init();
32947
+ const sharedLessonKeys = (await memory.list()).map((entry) => entry.key).filter((key2) => key2.startsWith("lessons/"));
32948
+ const reviewAgent = {
32949
+ ...resolved,
32950
+ agent: {
32951
+ ...resolved.agent,
32952
+ modelOverride: resolved.agent.reviewModel,
32953
+ reasoningLevel: resolved.agent.reviewReasoningLevel
32954
+ },
32955
+ model: resolved.agent.reviewModel ?? resolved.model,
32956
+ reasoningLevel: resolved.agent.reviewReasoningLevel
32957
+ };
32958
+ const { apiKey, refreshApiKey } = await resolveAgentApiKey(db, authToken, reviewAgent, {
32959
+ withRefresher: true
32960
+ });
32961
+ const env = mergeSecretsIntoEnv(db, authToken);
32962
+ const proposals = await spawnReviewWorker(
32963
+ {
32964
+ mode: "review",
32965
+ review: {
32966
+ reviewId: claimed.id,
32967
+ evidence: digest.entries.map((entry) => ({
32968
+ sessionId: entry.sessionId,
32969
+ entryOrdinal: entry.ordinal
32970
+ }))
32971
+ },
32972
+ agent: reviewAgent,
32973
+ message: renderReviewInput(digest, privateLessons, sharedLessonKeys),
32974
+ enabledProviders: [...providerState_exports.listEnabled(db)],
32975
+ apiKey,
32976
+ turnId: claimed.id,
32977
+ bashApprovalMode: "auto_deny"
32978
+ },
32979
+ {
32980
+ signal: controller.signal,
32981
+ env,
32982
+ apiKeyRefreshHost: refreshApiKey ? { refresh: refreshApiKey } : void 0
32983
+ }
32984
+ );
32985
+ db.raw.transaction(() => {
32986
+ for (const proposal of proposals) {
32987
+ agentLessonProposals_exports.insert(db, {
32988
+ reviewId: claimed.id,
32989
+ agentId: claimed.agentId,
32990
+ scope: proposal.scope,
32991
+ text: proposal.text,
32992
+ evidence: proposal.evidenceEntryIds
32993
+ });
32994
+ }
32995
+ agentReviews_exports.complete(db, claimed.id, proposals.length);
32996
+ })();
32997
+ } catch (error2) {
32998
+ if (controller.signal.aborted) {
32999
+ agentReviews_exports.cancel(db, claimed.id, "cancelled");
33000
+ } else {
33001
+ agentReviews_exports.fail(db, claimed.id, error2 instanceof Error ? error2.message : String(error2));
33002
+ }
33003
+ } finally {
33004
+ if (registered) unregisterAgent(claimed.agentId);
33005
+ }
33006
+ }
33007
+ async function dispatchClaimableReviews(now = Date.now()) {
33008
+ const { db } = getCtx();
33009
+ await Promise.allSettled(
33010
+ agentReviews_exports.listClaimable(db, now).map((review) => dispatchAgentReview(review.id))
33011
+ );
33012
+ }
33013
+
32134
33014
  // ../daemon/src/lib/turn-outcome.ts
32135
33015
  function turnFrameFailure(frame) {
32136
33016
  if (frame.kind === "fatal") return frame.error;
@@ -32216,7 +33096,7 @@ async function fireTrigger(dispatch2) {
32216
33096
  const controller = new AbortController();
32217
33097
  let registered = false;
32218
33098
  try {
32219
- const claim2 = claimSchedulerTrigger(ctx.db, {
33099
+ const claim3 = claimSchedulerTrigger(ctx.db, {
32220
33100
  dispatchId: claimedDispatch.id,
32221
33101
  triggerId: t.id,
32222
33102
  agentId: t.agentId,
@@ -32227,21 +33107,21 @@ async function fireTrigger(dispatch2) {
32227
33107
  registered = true;
32228
33108
  }
32229
33109
  });
32230
- if (claim2.kind === "already_claimed") {
33110
+ if (claim3.kind === "already_claimed") {
32231
33111
  releaseLease();
32232
33112
  triggerDispatches_exports.succeed(ctx.db, claimedDispatch.id);
32233
33113
  s.firing.delete(key2);
32234
33114
  return;
32235
33115
  }
32236
- if (claim2.kind === "approval_pending") {
33116
+ if (claim3.kind === "approval_pending") {
32237
33117
  releaseLease();
32238
33118
  triggerDispatches_exports.defer(ctx.db, claimedDispatch.id, Date.now() + TICK_MS);
32239
33119
  s.firing.delete(key2);
32240
33120
  return;
32241
33121
  }
32242
- if (claim2.kind === "approval_terminal") {
33122
+ if (claim3.kind === "approval_terminal") {
32243
33123
  releaseLease();
32244
- triggerDispatches_exports.fail(ctx.db, claimedDispatch.id, claim2.reason, { maxAttempts: 1 });
33124
+ triggerDispatches_exports.fail(ctx.db, claimedDispatch.id, claim3.reason, { maxAttempts: 1 });
32245
33125
  s.firing.delete(key2);
32246
33126
  return;
32247
33127
  }
@@ -32370,7 +33250,8 @@ async function fireInboxWake(agentId) {
32370
33250
  acquiredLeaseRelease: releaseLease,
32371
33251
  controller,
32372
33252
  alreadyRegistered: true,
32373
- bashApprovalMode: "auto_deny"
33253
+ bashApprovalMode: "auto_deny",
33254
+ causalParentMessageId: selectCausalParent(msgs)
32374
33255
  })) {
32375
33256
  if (frame.kind === "fatal") {
32376
33257
  console.error(`[scheduler] inbox wake ${agentId} fatal:`, frame.error);
@@ -32423,6 +33304,7 @@ async function tick() {
32423
33304
  for (const agentId of recipients) {
32424
33305
  work.push(fireInboxWake(agentId));
32425
33306
  }
33307
+ work.push(dispatchClaimableReviews(now));
32426
33308
  await Promise.allSettled(work);
32427
33309
  }
32428
33310
  function startScheduler() {
@@ -32541,7 +33423,7 @@ async function authMiddleware(c, next) {
32541
33423
  }
32542
33424
 
32543
33425
  // ../daemon/src/routes/agents.ts
32544
- import { randomUUID as randomUUID19 } from "crypto";
33426
+ import { randomUUID as randomUUID22 } from "crypto";
32545
33427
  import { existsSync as existsSync17, readdirSync as readdirSync10, readFileSync as readFileSync13, rmSync as rmSync13 } from "fs";
32546
33428
  import { join as join21 } from "path";
32547
33429
 
@@ -32574,6 +33456,47 @@ function resolveAgentIdParam(db, raw2) {
32574
33456
  return agents_exports.resolveId(db, raw2) ?? raw2;
32575
33457
  }
32576
33458
 
33459
+ // ../daemon/src/lib/lesson-decisions.ts
33460
+ var LessonProposalConflictError = class extends Error {
33461
+ };
33462
+ function conflict() {
33463
+ throw new LessonProposalConflictError("proposal changed or is no longer eligible");
33464
+ }
33465
+ async function approveLessonProposal(db, memory, id, version2) {
33466
+ const current = agentLessonProposals_exports.get(db, id);
33467
+ if (!current || current.version !== version2 || current.status !== "pending") return conflict();
33468
+ if (current.scope === "private") {
33469
+ return agentLessonProposals_exports.approve(db, id, version2, null) ?? conflict();
33470
+ }
33471
+ const key2 = `lessons/${current.id}.md`;
33472
+ await memory.init();
33473
+ await memory.write(
33474
+ key2,
33475
+ `# Reviewed lesson
33476
+
33477
+ ${current.text}
33478
+
33479
+ Source: agent ${current.agentId}, review ${current.reviewId}.
33480
+ `
33481
+ );
33482
+ const approved = agentLessonProposals_exports.approve(db, id, version2, key2);
33483
+ if (approved) return approved;
33484
+ await memory.remove(key2);
33485
+ return conflict();
33486
+ }
33487
+ function rejectLessonProposal(db, id, version2) {
33488
+ return agentLessonProposals_exports.reject(db, id, version2) ?? conflict();
33489
+ }
33490
+ async function revokeLessonProposal(db, memory, id, version2) {
33491
+ const current = agentLessonProposals_exports.get(db, id);
33492
+ if (!current || current.version !== version2 || current.status !== "approved") return conflict();
33493
+ if (current.appliedKey) {
33494
+ await memory.init();
33495
+ await memory.remove(current.appliedKey);
33496
+ }
33497
+ return agentLessonProposals_exports.revoke(db, id, version2) ?? conflict();
33498
+ }
33499
+
32577
33500
  // ../daemon/src/lib/telegram/bot.ts
32578
33501
  import { Bot, GrammyError } from "grammy";
32579
33502
 
@@ -34548,13 +35471,13 @@ agentsRouter.post("/", async (c) => {
34548
35471
  return c.json({ agent, team: teamPolicies_exports.detail(db, agent.teamId) }, 201);
34549
35472
  } catch (err) {
34550
35473
  const message = err.message;
34551
- const conflict = message.startsWith("placement_required") || message.startsWith("team_policy_invalid") || message.startsWith("team_revision_conflict");
35474
+ const conflict2 = message.startsWith("placement_required") || message.startsWith("team_policy_invalid") || message.startsWith("team_revision_conflict");
34552
35475
  return c.json(
34553
35476
  {
34554
35477
  error: message,
34555
35478
  ...message.startsWith("placement_required") ? { code: "placement_required" } : message.startsWith("team_revision_conflict") ? { code: "team_revision_conflict" } : message.startsWith("team_policy_invalid") ? { code: "team_policy_invalid" } : {}
34556
35479
  },
34557
- conflict ? 409 : 400
35480
+ conflict2 ? 409 : 400
34558
35481
  );
34559
35482
  }
34560
35483
  });
@@ -34844,13 +35767,13 @@ agentsRouter.patch("/:id/team", async (c) => {
34844
35767
  });
34845
35768
  } catch (err) {
34846
35769
  const message = err.message;
34847
- const conflict = message.startsWith("agent_turn_active") || message.startsWith("placement_required") || message.startsWith("team_policy_invalid") || message.includes("team_revision_conflict");
35770
+ const conflict2 = message.startsWith("agent_turn_active") || message.startsWith("placement_required") || message.startsWith("team_policy_invalid") || message.includes("team_revision_conflict");
34848
35771
  return c.json(
34849
35772
  {
34850
35773
  error: message,
34851
35774
  ...message.startsWith("agent_turn_active") ? { code: "agent_turn_active" } : message.startsWith("placement_required") ? { code: "placement_required" } : message.includes("team_revision_conflict") ? { code: message.split(":")[0] } : message.startsWith("team_policy_invalid") ? { code: "team_policy_invalid" } : {}
34852
35775
  },
34853
- conflict ? 409 : 400
35776
+ conflict2 ? 409 : 400
34854
35777
  );
34855
35778
  }
34856
35779
  });
@@ -34908,6 +35831,149 @@ agentsRouter.delete("/:id/skills/:name", (c) => {
34908
35831
  agents_exports.detachSkill(db, id, c.req.param("name"));
34909
35832
  return c.body(null, 204);
34910
35833
  });
35834
+ agentsRouter.get("/:id/review-config", (c) => {
35835
+ const { db } = getCtx();
35836
+ const agent = agents_exports.get(db, c.req.param("id"));
35837
+ if (!agent) return c.json({ error: `agent not found: ${c.req.param("id")}` }, 404);
35838
+ return c.json({
35839
+ enabled: agent.reviewEnabled,
35840
+ everyNTurns: agent.reviewEveryNTurns,
35841
+ model: agent.reviewModel,
35842
+ reasoningLevel: agent.reviewReasoningLevel,
35843
+ turnsSinceLast: agent.reviewTurnsSinceLast
35844
+ });
35845
+ });
35846
+ agentsRouter.patch("/:id/review-config", async (c) => {
35847
+ const body = await c.req.json().catch(() => null);
35848
+ if (!body) return c.json({ error: "invalid JSON body" }, 400);
35849
+ const { db } = getCtx();
35850
+ const agent = agents_exports.get(db, c.req.param("id"));
35851
+ if (!agent) return c.json({ error: `agent not found: ${c.req.param("id")}` }, 404);
35852
+ const everyNTurns = body.everyNTurns ?? agent.reviewEveryNTurns;
35853
+ const reasoningLevel = body.reasoningLevel ?? agent.reviewReasoningLevel;
35854
+ if (!Number.isInteger(everyNTurns) || everyNTurns < 1 || everyNTurns > 100) {
35855
+ return c.json({ error: "everyNTurns must be an integer from 1 to 100" }, 400);
35856
+ }
35857
+ if (!REASONING_LEVELS.includes(reasoningLevel)) {
35858
+ return c.json({ error: `invalid reasoningLevel: ${reasoningLevel}` }, 400);
35859
+ }
35860
+ if (body.model !== void 0 && body.model !== null && !body.model.includes(":")) {
35861
+ return c.json({ error: "model must use provider:model format" }, 400);
35862
+ }
35863
+ agents_exports.setReviewConfig(db, agent.id, {
35864
+ enabled: body.enabled ?? agent.reviewEnabled,
35865
+ everyNTurns,
35866
+ model: body.model === void 0 ? agent.reviewModel : body.model,
35867
+ reasoningLevel
35868
+ });
35869
+ const updated = agents_exports.get(db, agent.id);
35870
+ if (!updated) return c.json({ error: "agent disappeared" }, 500);
35871
+ return c.json({
35872
+ enabled: updated.reviewEnabled,
35873
+ everyNTurns: updated.reviewEveryNTurns,
35874
+ model: updated.reviewModel,
35875
+ reasoningLevel: updated.reviewReasoningLevel,
35876
+ turnsSinceLast: updated.reviewTurnsSinceLast
35877
+ });
35878
+ });
35879
+ agentsRouter.post("/:id/reviews", (c) => {
35880
+ const { db } = getCtx();
35881
+ const agent = agents_exports.get(db, c.req.param("id"));
35882
+ if (!agent) return c.json({ error: `agent not found: ${c.req.param("id")}` }, 404);
35883
+ return c.json(
35884
+ { review: agentReviews_exports.enqueueManual(db, agent.id) },
35885
+ 202
35886
+ );
35887
+ });
35888
+ agentsRouter.get("/:id/reviews", (c) => {
35889
+ const { db } = getCtx();
35890
+ const agent = agents_exports.get(db, c.req.param("id"));
35891
+ if (!agent) return c.json({ error: `agent not found: ${c.req.param("id")}` }, 404);
35892
+ return c.json({
35893
+ reviews: agentReviews_exports.listForAgent(db, agent.id)
35894
+ });
35895
+ });
35896
+ agentsRouter.get("/:id/reviews/:reviewId", (c) => {
35897
+ const { db, paths } = getCtx();
35898
+ const agent = agents_exports.get(db, c.req.param("id"));
35899
+ const review = agentReviews_exports.get(db, c.req.param("reviewId"));
35900
+ if (!agent || !review || review.agentId !== agent.id)
35901
+ return c.json({ error: "review not found" }, 404);
35902
+ return c.json({
35903
+ review,
35904
+ proposals: enrichProposalEvidence(
35905
+ agentLessonProposals_exports.listForReview(db, review.id),
35906
+ resolveAgent(db, paths, agent.id),
35907
+ paths
35908
+ )
35909
+ });
35910
+ });
35911
+ agentsRouter.get("/:id/lesson-proposals", (c) => {
35912
+ const { db, paths } = getCtx();
35913
+ const agent = agents_exports.get(db, c.req.param("id"));
35914
+ if (!agent) return c.json({ error: `agent not found: ${c.req.param("id")}` }, 404);
35915
+ const rawStatus = c.req.query("status");
35916
+ const statuses = ["pending", "approved", "rejected", "revoked"];
35917
+ if (rawStatus && !statuses.includes(rawStatus)) {
35918
+ return c.json({ error: `invalid status: ${rawStatus}` }, 400);
35919
+ }
35920
+ return c.json({
35921
+ proposals: enrichProposalEvidence(
35922
+ agentLessonProposals_exports.listForAgent(db, agent.id, {
35923
+ status: rawStatus
35924
+ }),
35925
+ resolveAgent(db, paths, agent.id),
35926
+ paths
35927
+ )
35928
+ });
35929
+ });
35930
+ agentsRouter.patch("/:id/lesson-proposals/:proposalId", async (c) => {
35931
+ const body = await c.req.json().catch(() => null);
35932
+ if (!body || !Number.isInteger(body.version)) return c.json({ error: "version is required" }, 400);
35933
+ const { db } = getCtx();
35934
+ const agent = agents_exports.get(db, c.req.param("id"));
35935
+ const current = agentLessonProposals_exports.get(db, c.req.param("proposalId"));
35936
+ if (!agent || !current || current.agentId !== agent.id)
35937
+ return c.json({ error: "proposal not found" }, 404);
35938
+ const text2 = body.text?.trim() ?? current.text;
35939
+ const scope = body.scope ?? current.scope;
35940
+ if (!text2 || text2.length > 500) return c.json({ error: "text must be 1-500 characters" }, 400);
35941
+ if (!["private", "shared"].includes(scope)) {
35942
+ return c.json({ error: `invalid scope: ${scope}` }, 400);
35943
+ }
35944
+ const proposal = agentLessonProposals_exports.updatePending(db, current.id, body.version, {
35945
+ text: text2,
35946
+ scope
35947
+ });
35948
+ return proposal ? c.json({ proposal }) : c.json({ error: "proposal changed or is no longer pending" }, 409);
35949
+ });
35950
+ for (const decision of ["approve", "reject", "revoke"]) {
35951
+ agentsRouter.post(`/:id/lesson-proposals/:proposalId/${decision}`, async (c) => {
35952
+ const body = await c.req.json().catch(() => null);
35953
+ if (!body || !Number.isInteger(body.version))
35954
+ return c.json({ error: "version is required" }, 400);
35955
+ const { db, paths } = getCtx();
35956
+ const agent = agents_exports.get(db, c.req.param("id"));
35957
+ const current = agentLessonProposals_exports.get(db, c.req.param("proposalId"));
35958
+ if (!agent || !current || current.agentId !== agent.id)
35959
+ return c.json({ error: "proposal not found" }, 404);
35960
+ const team = teams_exports.get(db, agent.teamId, paths);
35961
+ if (!team) return c.json({ error: `team not found: ${agent.teamId}` }, 404);
35962
+ try {
35963
+ const proposal = await runAgentLifecycleMutation(agent.id, async () => {
35964
+ const memory = qmdBackend(join21(team.path, "memory"));
35965
+ if (decision === "approve")
35966
+ return approveLessonProposal(db, memory, current.id, body.version);
35967
+ if (decision === "reject") return rejectLessonProposal(db, current.id, body.version);
35968
+ return revokeLessonProposal(db, memory, current.id, body.version);
35969
+ });
35970
+ return c.json({ proposal });
35971
+ } catch (error2) {
35972
+ if (error2 instanceof LessonProposalConflictError) return c.json({ error: error2.message }, 409);
35973
+ throw error2;
35974
+ }
35975
+ });
35976
+ }
34911
35977
  agentsRouter.get("/:id/triggers", (c) => {
34912
35978
  const { db, paths, authToken } = getCtx();
34913
35979
  const agent = agents_exports.get(db, c.req.param("id"));
@@ -34974,6 +36040,13 @@ agentsRouter.get("/:id/messages", (c) => {
34974
36040
  };
34975
36041
  return c.json(body);
34976
36042
  });
36043
+ agentsRouter.get("/:id/loop-breaks", (c) => {
36044
+ const { db } = getCtx();
36045
+ const agent = agents_exports.get(db, c.req.param("id"));
36046
+ if (!agent) return c.json({ error: `agent not found: ${c.req.param("id")}` }, 404);
36047
+ const limit = Math.min(Math.max(Number(c.req.query("limit")) || 25, 1), 100);
36048
+ return c.json({ events: listAgentLoopBreaks(db, agent.id, limit) });
36049
+ });
34977
36050
  agentsRouter.post("/:id/messages", async (c) => {
34978
36051
  const body = await c.req.json().catch(() => null);
34979
36052
  if (!body || typeof body.from !== "string" || !body.from || !body.payload || typeof body.payload.text !== "string") {
@@ -34999,6 +36072,9 @@ agentsRouter.post("/:id/messages", async (c) => {
34999
36072
  });
35000
36073
  return c.json(msg, 201);
35001
36074
  } catch (error2) {
36075
+ if (error2 instanceof AgentLoopLimitError) {
36076
+ return c.json({ error: error2.message, code: "agent_loop_limit", event: error2.event }, 429);
36077
+ }
35002
36078
  if (error2 instanceof CommunicationDeniedError) {
35003
36079
  return c.json(
35004
36080
  { ...error2.result, attemptKind: error2.attemptKind, attemptId: error2.attemptId },
@@ -35070,7 +36146,7 @@ agentsRouter.post("/:id/chat", async (c) => {
35070
36146
  if (!message && attachments.length === 0) {
35071
36147
  return c.json({ error: "message or an attachment is required" }, 400);
35072
36148
  }
35073
- const requestAttemptId = c.req.header("x-request-id") ?? randomUUID19();
36149
+ const requestAttemptId = c.req.header("x-request-id") ?? randomUUID22();
35074
36150
  try {
35075
36151
  authorizeUserIngress(db, id, {
35076
36152
  origin: "http_chat",
@@ -35451,6 +36527,24 @@ function countProperties(schema) {
35451
36527
  if (!props || typeof props !== "object") return null;
35452
36528
  return Object.keys(props).length;
35453
36529
  }
36530
+ function enrichProposalEvidence(proposals, agent, paths) {
36531
+ const sessions = /* @__PURE__ */ new Map();
36532
+ return proposals.map((proposal) => ({
36533
+ ...proposal,
36534
+ evidence: proposal.evidence.map((evidence) => {
36535
+ let messages = sessions.get(evidence.sessionId);
36536
+ if (!messages) {
36537
+ messages = piMessagesToProviderView(loadSessionMessages(agent, paths, evidence.sessionId));
36538
+ sessions.set(evidence.sessionId, messages);
36539
+ }
36540
+ const message = messages[evidence.entryOrdinal];
36541
+ const excerpt = message ? redactReviewText(
36542
+ message.role === "tool" ? `[tool:${message.toolName ?? "unknown"}] completed` : message.content
36543
+ ).replace(/\s+/g, " ").slice(0, 240) : void 0;
36544
+ return { ...evidence, ...excerpt ? { excerpt } : {} };
36545
+ })
36546
+ }));
36547
+ }
35454
36548
 
35455
36549
  // ../daemon/src/routes/approvals.ts
35456
36550
  import { Bot as Bot2, InputFile as InputFile2 } from "grammy";
@@ -35598,7 +36692,19 @@ function validateSchedulerGrant(db, approval) {
35598
36692
  async function deliver(approval) {
35599
36693
  if (approval.payloadKind === "agent_message") {
35600
36694
  const payload = approval.payload;
35601
- messages_exports.send(getCtx().db, payload);
36695
+ const causality = resolveMessageCausality(getCtx().db, payload);
36696
+ const loopBreak = enforceMessageCausality(getCtx().db, {
36697
+ from: payload.from,
36698
+ to: payload.to,
36699
+ origin: "communication_approval_delivery",
36700
+ causality
36701
+ });
36702
+ if (loopBreak) throw new AgentLoopLimitError(loopBreak);
36703
+ messages_exports.send(getCtx().db, {
36704
+ ...payload,
36705
+ causalChainId: causality.causalChainId,
36706
+ causalHop: causality.causalHop
36707
+ });
35602
36708
  return;
35603
36709
  }
35604
36710
  if (approval.payloadKind === "scheduler_trigger") {
@@ -35734,11 +36840,54 @@ function approvalError(error2) {
35734
36840
  return Response.json({ error: message }, { status: 400 });
35735
36841
  }
35736
36842
 
36843
+ // ../daemon/src/routes/attention.ts
36844
+ import { Hono as Hono3 } from "hono";
36845
+ var attentionRouter = new Hono3();
36846
+ attentionRouter.get("/", (c) => {
36847
+ const state3 = c.req.query("state") ?? "open";
36848
+ if (!["open", "acknowledged", "all"].includes(state3))
36849
+ return c.json({ error: "invalid attention state", code: "invalid_attention_state" }, 400);
36850
+ const kind = c.req.query("kind");
36851
+ if (kind && !ATTENTION_KINDS.includes(kind))
36852
+ return c.json({ error: "invalid attention kind", code: "invalid_attention_kind" }, 400);
36853
+ const rawLimit = c.req.query("limit");
36854
+ const limit = rawLimit === void 0 ? 100 : Number(rawLimit);
36855
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200)
36856
+ return c.json(
36857
+ { error: "attention limit must be between 1 and 200", code: "invalid_attention_limit" },
36858
+ 400
36859
+ );
36860
+ return c.json(projectAttention(getCtx().db, { state: state3, kind, limit }));
36861
+ });
36862
+ attentionRouter.get("/summary", (c) => c.json(attentionSummary(getCtx().db)));
36863
+ attentionRouter.post(
36864
+ "/acknowledge-all",
36865
+ (c) => c.json({ acknowledged: acknowledgeAllAttention(getCtx().db) })
36866
+ );
36867
+ attentionRouter.post("/:key/acknowledge", (c) => mutate(c, true));
36868
+ attentionRouter.delete("/:key/acknowledgement", (c) => mutate(c, false));
36869
+ function mutate(c, acknowledged) {
36870
+ try {
36871
+ return c.json({
36872
+ item: acknowledgeAttention(getCtx().db, c.req.param("key") ?? "", acknowledged)
36873
+ });
36874
+ } catch (error2) {
36875
+ const code = error2 instanceof Error ? error2.message : "attention_error";
36876
+ if (code === "invalid_attention_key")
36877
+ return c.json({ error: "invalid attention key", code }, 400);
36878
+ if (code === "attention_action_required")
36879
+ return c.json({ error: "action-required items must be resolved at their source", code }, 409);
36880
+ if (code === "attention_source_not_found")
36881
+ return c.json({ error: "attention source not found or no longer eligible", code }, 404);
36882
+ throw error2;
36883
+ }
36884
+ }
36885
+
35737
36886
  // ../daemon/src/routes/auth-login.ts
35738
36887
  import { spawn as spawn4 } from "child_process";
35739
- import { Hono as Hono3 } from "hono";
36888
+ import { Hono as Hono4 } from "hono";
35740
36889
  import { setCookie } from "hono/cookie";
35741
- var authRouter = new Hono3();
36890
+ var authRouter = new Hono4();
35742
36891
  authRouter.get("/auth/me", (c) => {
35743
36892
  const { db } = getCtx();
35744
36893
  return c.json({ authed: true, setupComplete: isSetupComplete(db) });
@@ -35852,8 +37001,8 @@ function openBrowser(url2) {
35852
37001
  }
35853
37002
 
35854
37003
  // ../daemon/src/routes/communication.ts
35855
- import { Hono as Hono4 } from "hono";
35856
- var communicationRouter = new Hono4();
37004
+ import { Hono as Hono5 } from "hono";
37005
+ var communicationRouter = new Hono5();
35857
37006
  communicationRouter.post("/evaluate", async (c) => {
35858
37007
  const input = await c.req.json().catch(() => null);
35859
37008
  if (!input || !input.source || !input.target || typeof input.origin !== "string" || typeof input.attemptKind !== "string" || typeof input.attemptId !== "string") {
@@ -35863,8 +37012,8 @@ communicationRouter.post("/evaluate", async (c) => {
35863
37012
  });
35864
37013
 
35865
37014
  // ../daemon/src/routes/config.ts
35866
- import { Hono as Hono5 } from "hono";
35867
- var configRouter = new Hono5();
37015
+ import { Hono as Hono6 } from "hono";
37016
+ var configRouter = new Hono6();
35868
37017
  configRouter.get("/providers", async (c) => {
35869
37018
  const { db, paths, authToken } = getCtx();
35870
37019
  const env = mergeSecretsIntoEnv(db, authToken);
@@ -36043,8 +37192,8 @@ function knownProviderRegistryNames() {
36043
37192
  }
36044
37193
 
36045
37194
  // ../daemon/src/routes/mcp.ts
36046
- import { Hono as Hono6 } from "hono";
36047
- var mcpRouter = new Hono6();
37195
+ import { Hono as Hono7 } from "hono";
37196
+ var mcpRouter = new Hono7();
36048
37197
  var TRANSPORTS = /* @__PURE__ */ new Set(["stdio", "http", "sse"]);
36049
37198
  function validate(input) {
36050
37199
  if (!input.name || !/^[a-zA-Z0-9_]+$/.test(input.name)) {
@@ -36173,8 +37322,8 @@ mcpRouter.post("/:id/test", async (c) => {
36173
37322
  });
36174
37323
 
36175
37324
  // ../daemon/src/routes/messages.ts
36176
- import { Hono as Hono7 } from "hono";
36177
- var messagesRouter = new Hono7();
37325
+ import { Hono as Hono8 } from "hono";
37326
+ var messagesRouter = new Hono8();
36178
37327
  messagesRouter.get("/:id", (c) => {
36179
37328
  const { db } = getCtx();
36180
37329
  const msg = messages_exports.get(db, c.req.param("id"));
@@ -36198,7 +37347,7 @@ messagesRouter.patch("/:id", async (c) => {
36198
37347
  import { existsSync as existsSync18 } from "fs";
36199
37348
  import { homedir as homedir2 } from "os";
36200
37349
  import { join as join24 } from "path";
36201
- import { Hono as Hono8 } from "hono";
37350
+ import { Hono as Hono9 } from "hono";
36202
37351
 
36203
37352
  // ../daemon/src/lib/backup.ts
36204
37353
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync14 } from "fs";
@@ -36293,7 +37442,7 @@ function createBackupArchive(paths, snapshot) {
36293
37442
  }
36294
37443
 
36295
37444
  // ../daemon/src/lib/daemon-liveness.ts
36296
- import { createHash as createHash5, randomUUID as randomUUID20 } from "crypto";
37445
+ import { createHash as createHash5, randomUUID as randomUUID23 } from "crypto";
36297
37446
  import {
36298
37447
  chmodSync as chmodSync2,
36299
37448
  closeSync as closeSync4,
@@ -36419,9 +37568,9 @@ function pidIsAlive(pid) {
36419
37568
  }
36420
37569
  function reclaimStaleRecord(path, existing) {
36421
37570
  const identity = createHash5("sha256").update(existing.instanceId ?? existing.raw).update(`\0${existing.stat.dev}\0${existing.stat.ino}`).digest("hex").slice(0, 24);
36422
- const claim2 = `${path}.reclaim-${identity}`;
37571
+ const claim3 = `${path}.reclaim-${identity}`;
36423
37572
  try {
36424
- mkdirSync9(claim2, { mode: 448 });
37573
+ mkdirSync9(claim3, { mode: 448 });
36425
37574
  } catch (error2) {
36426
37575
  if (errorCode(error2) === "EEXIST") return null;
36427
37576
  throw error2;
@@ -36431,8 +37580,8 @@ function reclaimStaleRecord(path, existing) {
36431
37580
  if (!current || !sameFile(current.stat, existing.stat) || current.raw !== existing.raw) {
36432
37581
  return null;
36433
37582
  }
36434
- renameSync3(path, join23(claim2, "stale-record.json"));
36435
- return claim2;
37583
+ renameSync3(path, join23(claim3, "stale-record.json"));
37584
+ return claim3;
36436
37585
  } catch (error2) {
36437
37586
  if (!["ENOENT", "EEXIST"].includes(errorCode(error2) ?? "")) throw error2;
36438
37587
  return null;
@@ -36460,7 +37609,7 @@ function acquireDaemonLiveness(paths) {
36460
37609
  mkdirSync9(dirname5(resolve6(paths.home)), { recursive: true });
36461
37610
  const path = daemonLivenessPath(paths.home);
36462
37611
  mkdirSync9(dirname5(path), { recursive: true });
36463
- const instanceId = randomUUID20();
37612
+ const instanceId = randomUUID23();
36464
37613
  const startedAt = Date.now();
36465
37614
  let fd = null;
36466
37615
  let reclaimClaim = null;
@@ -36494,8 +37643,8 @@ function acquireDaemonLiveness(paths) {
36494
37643
  `another Bazilion daemon owns this home${owner}; stop it before starting another`
36495
37644
  );
36496
37645
  }
36497
- const claim2 = reclaimStaleRecord(path, existing);
36498
- if (claim2) reclaimClaim = claim2;
37646
+ const claim3 = reclaimStaleRecord(path, existing);
37647
+ if (claim3) reclaimClaim = claim3;
36499
37648
  }
36500
37649
  }
36501
37650
  if (fd === null) {
@@ -36580,7 +37729,7 @@ function acquireDaemonLiveness(paths) {
36580
37729
  }
36581
37730
 
36582
37731
  // ../daemon/src/routes/misc.ts
36583
- var miscRouter = new Hono8();
37732
+ var miscRouter = new Hono9();
36584
37733
  miscRouter.get("/health", (c) => {
36585
37734
  const instanceId = getDaemonInstanceId();
36586
37735
  if (instanceId) c.header("x-bazilion-daemon-instance", instanceId);
@@ -36802,8 +37951,8 @@ miscRouter.delete("/tokens/:id", (c) => {
36802
37951
  // ../daemon/src/routes/profiles.ts
36803
37952
  import { existsSync as existsSync19, readFileSync as readFileSync15, writeFileSync as writeFileSync12 } from "fs";
36804
37953
  import { join as join25 } from "path";
36805
- import { Hono as Hono9 } from "hono";
36806
- var profilesRouter = new Hono9();
37954
+ import { Hono as Hono10 } from "hono";
37955
+ var profilesRouter = new Hono10();
36807
37956
  profilesRouter.get("/", (c) => {
36808
37957
  const { db } = getCtx();
36809
37958
  const profiles = profiles_exports.list(db);
@@ -36982,8 +38131,8 @@ function parseCommunicationDefaults(value) {
36982
38131
  }
36983
38132
 
36984
38133
  // ../daemon/src/routes/shell-approvals.ts
36985
- import { Hono as Hono10 } from "hono";
36986
- var shellApprovalsRouter = new Hono10();
38134
+ import { Hono as Hono11 } from "hono";
38135
+ var shellApprovalsRouter = new Hono11();
36987
38136
  shellApprovalsRouter.get("/", (c) => {
36988
38137
  const agentId = c.req.query("agentId")?.trim();
36989
38138
  if (!agentId) return c.json({ error: "agentId query parameter is required" }, 400);
@@ -37018,9 +38167,9 @@ shellApprovalsRouter.post("/:id", async (c) => {
37018
38167
  import { existsSync as existsSync20, mkdtempSync as mkdtempSync4, rmSync as rmSync16, writeFileSync as writeFileSync13 } from "fs";
37019
38168
  import { homedir as homedir3, tmpdir as tmpdir4 } from "os";
37020
38169
  import { join as join26 } from "path";
37021
- import { Hono as Hono11 } from "hono";
38170
+ import { Hono as Hono12 } from "hono";
37022
38171
  var MAX_ZIP_BYTES = 50 * 1024 * 1024;
37023
- var skillsRouter = new Hono11();
38172
+ var skillsRouter = new Hono12();
37024
38173
  skillsRouter.get("/", (c) => {
37025
38174
  const { db, paths } = getCtx();
37026
38175
  const out = [];
@@ -37137,9 +38286,9 @@ async function parseImportInput(request2) {
37137
38286
  }
37138
38287
 
37139
38288
  // ../daemon/src/routes/team-templates.ts
37140
- import { randomUUID as randomUUID21 } from "crypto";
37141
- import { Hono as Hono12 } from "hono";
37142
- var teamTemplatesRouter = new Hono12();
38289
+ import { randomUUID as randomUUID24 } from "crypto";
38290
+ import { Hono as Hono13 } from "hono";
38291
+ var teamTemplatesRouter = new Hono13();
37143
38292
  teamTemplatesRouter.get("/", (c) => {
37144
38293
  const { db } = getCtx();
37145
38294
  return c.json(
@@ -37177,7 +38326,7 @@ teamTemplatesRouter.post("/import", async (c) => {
37177
38326
  if (!profiles_exports.get(db, parsed.profileId)) {
37178
38327
  throw new Error(`profile not found: ${parsed.profileId}`);
37179
38328
  }
37180
- const slotId = randomUUID21();
38329
+ const slotId = randomUUID24();
37181
38330
  keyToSlot.set(clientKey, slotId);
37182
38331
  return { ...parsed, slotId };
37183
38332
  });
@@ -37470,9 +38619,9 @@ function canonicalError(c, error2) {
37470
38619
 
37471
38620
  // ../daemon/src/routes/teams.ts
37472
38621
  import { join as join27 } from "path";
37473
- import { Hono as Hono13 } from "hono";
38622
+ import { Hono as Hono14 } from "hono";
37474
38623
  var USER_MD_MAX_BYTES2 = 12e3;
37475
- var teamsRouter = new Hono13();
38624
+ var teamsRouter = new Hono14();
37476
38625
  teamsRouter.get("/", (c) => {
37477
38626
  const { db, paths } = getCtx();
37478
38627
  return c.json(teams_exports.list(db, paths));
@@ -37841,7 +38990,7 @@ function teamPolicyError(c, error2) {
37841
38990
  }
37842
38991
 
37843
38992
  // ../daemon/src/routes/telegram.ts
37844
- import { Hono as Hono14 } from "hono";
38993
+ import { Hono as Hono15 } from "hono";
37845
38994
 
37846
38995
  // ../daemon/src/lib/telegram/preflight.ts
37847
38996
  var TELEGRAM_API_BASE = "https://api.telegram.org";
@@ -37870,7 +39019,7 @@ async function runPreflight(args) {
37870
39019
  clearTimeout(t);
37871
39020
  }
37872
39021
  };
37873
- const fail2 = (step, message) => ({
39022
+ const fail3 = (step, message) => ({
37874
39023
  configured: true,
37875
39024
  preflight: null,
37876
39025
  error: { step, message },
@@ -37880,13 +39029,13 @@ async function runPreflight(args) {
37880
39029
  try {
37881
39030
  me2 = await call("getMe", {});
37882
39031
  } catch (e) {
37883
- return fail2("getMe", errorMessage(e));
39032
+ return fail3("getMe", errorMessage(e));
37884
39033
  }
37885
39034
  let chat;
37886
39035
  try {
37887
39036
  chat = await call("getChat", { chat_id: chatId(args.chatId) });
37888
39037
  } catch (e) {
37889
- return fail2("getChat", errorMessage(e));
39038
+ return fail3("getChat", errorMessage(e));
37890
39039
  }
37891
39040
  let member;
37892
39041
  try {
@@ -37895,7 +39044,7 @@ async function runPreflight(args) {
37895
39044
  user_id: me2.id
37896
39045
  });
37897
39046
  } catch (e) {
37898
- return fail2("getChatMember", errorMessage(e));
39047
+ return fail3("getChatMember", errorMessage(e));
37899
39048
  }
37900
39049
  const hasManageTopics = member.status === "creator" || member.can_manage_topics === true;
37901
39050
  const preflight = {
@@ -37935,7 +39084,7 @@ var DERIVED_STATE_KEYS = [
37935
39084
  "TELEGRAM_DIRECTORY_MESSAGE_ID",
37936
39085
  MIGRATED_CHAT_ID_KEY
37937
39086
  ];
37938
- var telegramRouter = new Hono14();
39087
+ var telegramRouter = new Hono15();
37939
39088
  telegramRouter.get("/", (c) => {
37940
39089
  const { db, authToken } = getCtx();
37941
39090
  return c.json(readState(db, authToken));
@@ -38074,8 +39223,8 @@ function maskBotToken(t) {
38074
39223
  }
38075
39224
 
38076
39225
  // ../daemon/src/routes/triggers.ts
38077
- import { Hono as Hono15 } from "hono";
38078
- var triggersRouter = new Hono15();
39226
+ import { Hono as Hono16 } from "hono";
39227
+ var triggersRouter = new Hono16();
38079
39228
  triggersRouter.get("/:id/dispatches", (c) => {
38080
39229
  const { db } = getCtx();
38081
39230
  const id = c.req.param("id");
@@ -38108,9 +39257,10 @@ triggersRouter.patch("/:id", async (c) => {
38108
39257
 
38109
39258
  // ../daemon/src/app.ts
38110
39259
  function createApp() {
38111
- const app2 = new Hono16();
39260
+ const app2 = new Hono17();
38112
39261
  app2.use("*", authMiddleware);
38113
39262
  app2.route("/api/agents", agentsRouter);
39263
+ app2.route("/api/attention", attentionRouter);
38114
39264
  app2.route("/api/approvals", approvalsRouter);
38115
39265
  app2.route("/api/shell-approvals", shellApprovalsRouter);
38116
39266
  app2.route("/api/teams", teamsRouter);