nodal-agents 0.6.7 → 0.7.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 (153) hide show
  1. package/README.md +1 -1
  2. package/cli.js +24 -0
  3. package/migrations/0051_memory_search_fts.sql +2 -0
  4. package/migrations/0052_entities_memory_curation.sql +1 -0
  5. package/migrations/0053_memory_importance_locked.sql +1 -0
  6. package/migrations/meta/_journal.json +21 -0
  7. package/package.json +1 -1
  8. package/runner.js +275 -57
  9. package/web/.next/BUILD_ID +1 -1
  10. package/web/.next/app-path-routes-manifest.json +2 -2
  11. package/web/.next/build-manifest.json +2 -2
  12. package/web/.next/prerender-manifest.json +3 -3
  13. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js +3 -3
  14. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page_client-reference-manifest.js +1 -1
  15. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js +2 -2
  16. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js.nft.json +1 -1
  17. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page_client-reference-manifest.js +1 -1
  18. package/web/.next/server/app/(dashboard)/agents/page.js +2 -2
  19. package/web/.next/server/app/(dashboard)/agents/page.js.nft.json +1 -1
  20. package/web/.next/server/app/(dashboard)/agents/page_client-reference-manifest.js +1 -1
  21. package/web/.next/server/app/(dashboard)/approvals/page.js +2 -2
  22. package/web/.next/server/app/(dashboard)/approvals/page.js.nft.json +1 -1
  23. package/web/.next/server/app/(dashboard)/approvals/page_client-reference-manifest.js +1 -1
  24. package/web/.next/server/app/(dashboard)/automations/page.js +2 -2
  25. package/web/.next/server/app/(dashboard)/automations/page.js.nft.json +1 -1
  26. package/web/.next/server/app/(dashboard)/automations/page_client-reference-manifest.js +1 -1
  27. package/web/.next/server/app/(dashboard)/chat/page.js +2 -2
  28. package/web/.next/server/app/(dashboard)/chat/page.js.nft.json +1 -1
  29. package/web/.next/server/app/(dashboard)/chat/page_client-reference-manifest.js +1 -1
  30. package/web/.next/server/app/(dashboard)/connectors/page.js +1 -1
  31. package/web/.next/server/app/(dashboard)/connectors/page.js.nft.json +1 -1
  32. package/web/.next/server/app/(dashboard)/connectors/page_client-reference-manifest.js +1 -1
  33. package/web/.next/server/app/(dashboard)/credentials/page.js +1 -1
  34. package/web/.next/server/app/(dashboard)/credentials/page.js.nft.json +1 -1
  35. package/web/.next/server/app/(dashboard)/credentials/page_client-reference-manifest.js +1 -1
  36. package/web/.next/server/app/(dashboard)/jobs/[id]/page.js +2 -2
  37. package/web/.next/server/app/(dashboard)/jobs/[id]/page.js.nft.json +1 -1
  38. package/web/.next/server/app/(dashboard)/jobs/[id]/page_client-reference-manifest.js +1 -1
  39. package/web/.next/server/app/(dashboard)/jobs/page.js +2 -2
  40. package/web/.next/server/app/(dashboard)/jobs/page.js.nft.json +1 -1
  41. package/web/.next/server/app/(dashboard)/jobs/page_client-reference-manifest.js +1 -1
  42. package/web/.next/server/app/(dashboard)/learned-skills/page.js +2 -2
  43. package/web/.next/server/app/(dashboard)/learned-skills/page_client-reference-manifest.js +1 -1
  44. package/web/.next/server/app/(dashboard)/llm-providers/page.js +1 -1
  45. package/web/.next/server/app/(dashboard)/llm-providers/page.js.nft.json +1 -1
  46. package/web/.next/server/app/(dashboard)/llm-providers/page_client-reference-manifest.js +1 -1
  47. package/web/.next/server/app/(dashboard)/logs/page.js +1 -1
  48. package/web/.next/server/app/(dashboard)/logs/page.js.nft.json +1 -1
  49. package/web/.next/server/app/(dashboard)/logs/page_client-reference-manifest.js +1 -1
  50. package/web/.next/server/app/(dashboard)/mcp/page.js +2 -2
  51. package/web/.next/server/app/(dashboard)/mcp/page_client-reference-manifest.js +1 -1
  52. package/web/.next/server/app/(dashboard)/memories/page.js +2 -2
  53. package/web/.next/server/app/(dashboard)/memories/page.js.nft.json +1 -1
  54. package/web/.next/server/app/(dashboard)/memories/page_client-reference-manifest.js +1 -1
  55. package/web/.next/server/app/(dashboard)/page.js +2 -2
  56. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  57. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  58. package/web/.next/server/app/(dashboard)/settings/page.js +2 -2
  59. package/web/.next/server/app/(dashboard)/settings/page.js.nft.json +1 -1
  60. package/web/.next/server/app/(dashboard)/settings/page_client-reference-manifest.js +1 -1
  61. package/web/.next/server/app/(dashboard)/settings/root-context/page.js +2 -2
  62. package/web/.next/server/app/(dashboard)/settings/root-context/page.js.nft.json +1 -1
  63. package/web/.next/server/app/(dashboard)/settings/root-context/page_client-reference-manifest.js +1 -1
  64. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js +2 -2
  65. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js.nft.json +1 -1
  66. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page_client-reference-manifest.js +1 -1
  67. package/web/.next/server/app/(dashboard)/skills/new/page.js +2 -2
  68. package/web/.next/server/app/(dashboard)/skills/new/page.js.nft.json +1 -1
  69. package/web/.next/server/app/(dashboard)/skills/new/page_client-reference-manifest.js +1 -1
  70. package/web/.next/server/app/(dashboard)/skills/page.js +2 -2
  71. package/web/.next/server/app/(dashboard)/skills/page_client-reference-manifest.js +1 -1
  72. package/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  73. package/web/.next/server/app/_global-error.html +1 -1
  74. package/web/.next/server/app/_global-error.rsc +2 -2
  75. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  76. package/web/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  77. package/web/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  78. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  79. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +2 -2
  80. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  81. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  82. package/web/.next/server/app/_not-found.html +1 -1
  83. package/web/.next/server/app/_not-found.rsc +3 -3
  84. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  85. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  86. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  87. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  88. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  89. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  90. package/web/.next/server/app/api/oauth/[provider]/callback/route.js +1 -1
  91. package/web/.next/server/app/login/page_client-reference-manifest.js +1 -1
  92. package/web/.next/server/app/onboarding/page.js +1319 -1315
  93. package/web/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  94. package/web/.next/server/app-paths-manifest.json +2 -2
  95. package/web/.next/server/chunks/2713.js +17 -13
  96. package/web/.next/server/chunks/3223.js +1 -1
  97. package/web/.next/server/chunks/4574.js +1 -1
  98. package/web/.next/server/chunks/4626.js +1 -0
  99. package/web/.next/server/chunks/8971.js +1 -0
  100. package/web/.next/server/chunks/9043.js +1 -0
  101. package/web/.next/server/middleware-build-manifest.js +1 -1
  102. package/web/.next/server/pages/404.html +1 -1
  103. package/web/.next/server/pages/500.html +1 -1
  104. package/web/.next/server/server-reference-manifest.js +1 -1
  105. package/web/.next/server/server-reference-manifest.json +1 -1
  106. package/web/.next/static/chunks/4964-08171c654e41b0a4.js +1 -0
  107. package/web/.next/static/chunks/{9836-14f7e30f77745e80.js → 9836-6830cb9d49ad261f.js} +1 -1
  108. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-fa5717f0d0450659.js +6 -0
  109. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/page-73012222871598aa.js +1 -0
  110. package/web/.next/static/chunks/app/(dashboard)/agents/page-7e9616b28c098e9e.js +1 -0
  111. package/web/.next/static/chunks/app/(dashboard)/approvals/{page-64cdc8de03714da8.js → page-9a01cb723a6ccff0.js} +1 -1
  112. package/web/.next/static/chunks/app/(dashboard)/automations/page-85c985cd687406ed.js +1 -0
  113. package/web/.next/static/chunks/app/(dashboard)/chat/page-a6cc7a58986c0340.js +1 -0
  114. package/web/.next/static/chunks/app/(dashboard)/connectors/{page-c417cceb9b25870c.js → page-21d6c042800c8cd3.js} +1 -1
  115. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/{page-4a258111bca49454.js → page-f6b993a88c70a2fe.js} +1 -1
  116. package/web/.next/static/chunks/app/(dashboard)/jobs/{page-4ef0c0002fb31c13.js → page-d0d6b9db1b68451f.js} +1 -1
  117. package/web/.next/static/chunks/app/(dashboard)/layout-d2ac2646967496bb.js +1 -0
  118. package/web/.next/static/chunks/app/(dashboard)/learned-skills/page-ec6312f6dfd7ee23.js +1 -0
  119. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-1fb26463eddf509c.js +1 -0
  120. package/web/.next/static/chunks/app/(dashboard)/logs/{page-69a02d29acd3d812.js → page-0bca733315e8a208.js} +1 -1
  121. package/web/.next/static/chunks/app/(dashboard)/mcp/page-ff56ecf2f65f5466.js +1 -0
  122. package/web/.next/static/chunks/app/(dashboard)/memories/page-fe4d9438c93750d6.js +1 -0
  123. package/web/.next/static/chunks/app/(dashboard)/page-9402fe2931216f4b.js +1 -0
  124. package/web/.next/static/chunks/app/(dashboard)/settings/page-995662f10ebff365.js +1 -0
  125. package/web/.next/static/chunks/app/(dashboard)/settings/root-context/page-785b6ce3e4c5890b.js +1 -0
  126. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-2106201c4df8b5a4.js +1 -0
  127. package/web/.next/static/chunks/app/(dashboard)/skills/new/page-0135bcb86ab60dc2.js +1 -0
  128. package/web/.next/static/chunks/app/(dashboard)/skills/{page-c47e251b87d38cc7.js → page-54e939d40c8b5b6d.js} +1 -1
  129. package/web/.next/static/chunks/app/onboarding/page-a4116e2f430eaff8.js +26 -0
  130. package/web/.next/static/css/eff119dd24f92093.css +3 -0
  131. package/web/.next/server/chunks/4088.js +0 -1
  132. package/web/.next/server/chunks/4122.js +0 -1
  133. package/web/.next/server/chunks/7495.js +0 -1
  134. package/web/.next/static/chunks/4964-09a4dc14acc324f3.js +0 -1
  135. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-499eb81b66c74dc5.js +0 -6
  136. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/page-e26c56155c37f38e.js +0 -1
  137. package/web/.next/static/chunks/app/(dashboard)/agents/page-f4c535f30d385857.js +0 -1
  138. package/web/.next/static/chunks/app/(dashboard)/automations/page-b654ebf59b7a0a76.js +0 -1
  139. package/web/.next/static/chunks/app/(dashboard)/chat/page-687c72476c5097c1.js +0 -1
  140. package/web/.next/static/chunks/app/(dashboard)/layout-04e80131398f1551.js +0 -1
  141. package/web/.next/static/chunks/app/(dashboard)/learned-skills/page-31b01dbe3baa44cc.js +0 -1
  142. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-96df36ea1bf14242.js +0 -1
  143. package/web/.next/static/chunks/app/(dashboard)/mcp/page-3ddd2e2c8973fb2f.js +0 -1
  144. package/web/.next/static/chunks/app/(dashboard)/memories/page-2e127cc802ee8e72.js +0 -1
  145. package/web/.next/static/chunks/app/(dashboard)/page-a25fd0011e21ba38.js +0 -1
  146. package/web/.next/static/chunks/app/(dashboard)/settings/page-06062fc5a2d536f6.js +0 -1
  147. package/web/.next/static/chunks/app/(dashboard)/settings/root-context/page-fd80a1645212a6d1.js +0 -1
  148. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-59bb2de0f81b5ed6.js +0 -1
  149. package/web/.next/static/chunks/app/(dashboard)/skills/new/page-0970daf889c085c9.js +0 -1
  150. package/web/.next/static/chunks/app/onboarding/page-995403cba38e26cc.js +0 -26
  151. package/web/.next/static/css/9fc24595c3687cfb.css +0 -3
  152. /package/web/.next/static/{vB-0ke6pDH6vYyme7Pz5G → KFac7DQ7gdp9XPQ2bnb-2}/_buildManifest.js +0 -0
  153. /package/web/.next/static/{vB-0ke6pDH6vYyme7Pz5G → KFac7DQ7gdp9XPQ2bnb-2}/_ssgManifest.js +0 -0
package/runner.js CHANGED
@@ -574,6 +574,10 @@ var init_memory = __esm({
574
574
  valid_to: z13.string().datetime().nullable(),
575
575
  fact_hash: z13.string().nullable(),
576
576
  archived: z13.boolean(),
577
+ // User override lock (migration 0053) — when true, the curator refuses to
578
+ // re-score importance for this fact. Defaulted so rows/objects predating
579
+ // the column still parse.
580
+ importance_locked: z13.boolean().default(false),
577
581
  last_accessed_at: z13.string().datetime().nullable(),
578
582
  access_count: z13.number().int().min(0),
579
583
  created_at: z13.string().datetime(),
@@ -585,6 +589,7 @@ var init_memory = __esm({
585
589
  updated_at: true,
586
590
  fact_hash: true,
587
591
  archived: true,
592
+ importance_locked: true,
588
593
  last_accessed_at: true,
589
594
  access_count: true,
590
595
  valid_from: true,
@@ -1593,6 +1598,10 @@ var init_entities = __esm({
1593
1598
  updatedAt: timestamp2("updated_at", { withTimezone: true }).defaultNow(),
1594
1599
  lastCuratorRunAt: timestamp2("last_curator_run_at", { withTimezone: true }),
1595
1600
  reflectionEnabled: boolean2("reflection_enabled").notNull().default(false),
1601
+ // Decoupled from reflection_enabled (which gates the skill-learning loop,
1602
+ // opt-in). Gates the memory curator (Phase 2 LLM pass) — ON by default so
1603
+ // memory curation runs even for entities that never opted into reflection.
1604
+ memoryCurationEnabled: boolean2("memory_curation_enabled").notNull().default(true),
1596
1605
  // Controls whether agent-authored skills are auto-assigned to the authoring
1597
1606
  // agent ('auto') or queued for the entity owner to approve ('approval').
1598
1607
  skillAssignmentMode: text2("skill_assignment_mode").notNull().default("approval").$type(),
@@ -2190,6 +2199,15 @@ var init_memory2 = __esm({
2190
2199
  id: uuid11("id").primaryKey().defaultRandom(),
2191
2200
  entityId: uuid11("entity_id").references(() => entities.id, { onDelete: "cascade" }),
2192
2201
  agentId: uuid11("agent_id").references(() => agents.id, { onDelete: "cascade" }),
2202
+ /**
2203
+ * Full-text source. A generated `search_tsv tsvector` column + GIN index
2204
+ * (raw SQL, migration 0051 — not expressible in the Drizzle schema builder,
2205
+ * same pattern as agent_jobs.search_tsv / migration 0050) makes this fact
2206
+ * rankable by relevance. Powers memory injection (selectMemoriesForInjection)
2207
+ * and the `query_memory` builtin (keywordSearchMemories) via ts_rank.
2208
+ * Config is `'english'` (stemming — "tokens" matches a "token" query),
2209
+ * unlike agent_jobs.search_tsv which stays `'simple'`.
2210
+ */
2193
2211
  fact: text11("fact").notNull(),
2194
2212
  category: text11("category").default("context"),
2195
2213
  importance: integer5("importance").default(3),
@@ -2201,6 +2219,12 @@ var init_memory2 = __esm({
2201
2219
  validTo: timestamp11("valid_to", { withTimezone: true }),
2202
2220
  factHash: text11("fact_hash"),
2203
2221
  archived: boolean6("archived").default(false),
2222
+ /**
2223
+ * User override lock (migration 0053). When true, the curator's
2224
+ * usage-driven re-scoring MUST refuse to touch importance — a user-pinned
2225
+ * value wins over the agent's guess and the curator's correction.
2226
+ */
2227
+ importanceLocked: boolean6("importance_locked").notNull().default(false),
2204
2228
  lastAccessedAt: timestamp11("last_accessed_at", { withTimezone: true }).defaultNow(),
2205
2229
  accessCount: integer5("access_count").default(0),
2206
2230
  createdAt: timestamp11("created_at", { withTimezone: true }).defaultNow(),
@@ -17705,6 +17729,17 @@ var envSchema = z.object({
17705
17729
  CURATOR_MEMORY_STALE_DAYS: z.coerce.number().default(60),
17706
17730
  CURATOR_MEMORY_IMPORTANCE_MAX: z.coerce.number().default(2),
17707
17731
  CURATOR_MEMORY_MIN: z.coerce.number().default(8),
17732
+ // Kill-switch semantics mirror REFLECTION_ENABLED but gate the MEMORY curator
17733
+ // (Phase 2 LLM pass) specifically — decoupled from the skill-learning loop:
17734
+ // MEMORY_CURATION_ENABLED='false' → global kill: disables memory curation
17735
+ // for every entity.
17736
+ // MEMORY_CURATION_ENABLED='' → per-entity decides (production default
17737
+ // (unset) when the var is absent). Since
17738
+ // entities.memory_curation_enabled
17739
+ // defaults TRUE, memory curation runs
17740
+ // out of the box — unlike reflection.
17741
+ // MEMORY_CURATION_ENABLED='true' → per-entity decides (same as unset).
17742
+ MEMORY_CURATION_ENABLED: z.string().default(""),
17708
17743
  // Optional. When set, the reflection + curator passes use THIS model id instead of
17709
17744
  // the agent's model — resolved against the agent's existing LLM key (ideal for an
17710
17745
  // OpenRouter key where one key serves many models; point the cheap/reliable
@@ -19575,13 +19610,6 @@ var WhitelistDriftError = class extends Error {
19575
19610
  undeclaredTools;
19576
19611
  code = "whitelist_drift";
19577
19612
  };
19578
- var WebSearchNotConfiguredError = class extends Error {
19579
- code = "web_search_not_configured";
19580
- constructor() {
19581
- super("web_search_not_configured");
19582
- this.name = "WebSearchNotConfiguredError";
19583
- }
19584
- };
19585
19613
 
19586
19614
  // ../../packages/tools/src/registry.ts
19587
19615
  function createToolRegistry() {
@@ -20033,6 +20061,7 @@ function rowToMemory(row) {
20033
20061
  valid_to: row.validTo?.toISOString() ?? null,
20034
20062
  fact_hash: row.factHash ?? null,
20035
20063
  archived: row.archived ?? false,
20064
+ importance_locked: row.importanceLocked ?? false,
20036
20065
  last_accessed_at: row.lastAccessedAt?.toISOString() ?? null,
20037
20066
  access_count: row.accessCount ?? 0,
20038
20067
  created_at: row.createdAt?.toISOString() ?? (/* @__PURE__ */ new Date()).toISOString(),
@@ -20058,19 +20087,29 @@ function keywordOrderBy(sort) {
20058
20087
  }
20059
20088
  async function keywordSearch(db, query, filters, sort = "importance") {
20060
20089
  const { entityId, agentId, skillTags, category, limit } = filters;
20061
- const words = query.replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 2).slice(0, 8);
20090
+ const orTerms = query.replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 2).slice(0, 8).map((w) => w.replace(/[^a-zA-Z0-9]/g, "")).filter((w) => w.length > 0);
20062
20091
  const conditions = buildWhereConditions({ entityId, agentId, skillTags, category });
20063
20092
  const orderBy = keywordOrderBy(sort);
20064
- if (words.length > 0) {
20065
- const ilikeConditions = words.map((w) => ilike2(agentMemory.fact, `%${w}%`));
20066
- const rows2 = await db.select().from(agentMemory).where(and3(...conditions, or3(...ilikeConditions))).orderBy(...orderBy).limit(limit);
20093
+ if (orTerms.length > 0) {
20094
+ const tsv = sql18`"agent_memory"."search_tsv"`;
20095
+ const tsq = sql18`to_tsquery('english', ${orTerms.join(" | ")})`;
20096
+ const rows2 = await db.select().from(agentMemory).where(and3(...conditions, sql18`${tsv} @@ ${tsq}`)).orderBy(sql18`ts_rank(${tsv}, ${tsq}) DESC`, ...orderBy).limit(limit);
20067
20097
  return rows2;
20068
20098
  }
20069
20099
  const rows = await db.select().from(agentMemory).where(and3(...conditions)).orderBy(...orderBy).limit(limit);
20070
20100
  return rows;
20071
20101
  }
20072
20102
  async function keywordSearchMemories(db, opts) {
20073
- const { query, entityId, agentId, skillTags, category, limit = 10, sort = "importance" } = opts;
20103
+ const {
20104
+ query,
20105
+ entityId,
20106
+ agentId,
20107
+ skillTags,
20108
+ category,
20109
+ limit = 10,
20110
+ sort = "importance",
20111
+ touch = true
20112
+ } = opts;
20074
20113
  const rows = await keywordSearch(
20075
20114
  db,
20076
20115
  query,
@@ -20078,7 +20117,7 @@ async function keywordSearchMemories(db, opts) {
20078
20117
  sort
20079
20118
  );
20080
20119
  const memories = rows.map((r) => rowToMemory(r));
20081
- if (memories.length > 0) {
20120
+ if (touch && memories.length > 0) {
20082
20121
  await touchMemories(
20083
20122
  db,
20084
20123
  memories.map((m) => m.id),
@@ -20150,6 +20189,10 @@ async function loadSource(db, entityId, memoryId) {
20150
20189
  const [row] = await db.select({ source: agentMemory.source }).from(agentMemory).where(and3(eq5(agentMemory.id, memoryId), eq5(agentMemory.entityId, entityId))).limit(1);
20151
20190
  return row ? row.source : void 0;
20152
20191
  }
20192
+ async function loadSourceAndLock(db, entityId, memoryId) {
20193
+ const [row] = await db.select({ source: agentMemory.source, importanceLocked: agentMemory.importanceLocked }).from(agentMemory).where(and3(eq5(agentMemory.id, memoryId), eq5(agentMemory.entityId, entityId))).limit(1);
20194
+ return row ? { source: row.source, importanceLocked: row.importanceLocked } : void 0;
20195
+ }
20153
20196
  async function archiveAgentMemory(db, entityId, memoryId) {
20154
20197
  const source = await loadSource(db, entityId, memoryId);
20155
20198
  if (source === void 0) return { error: "not_found" };
@@ -20165,6 +20208,17 @@ async function updateAgentMemoryFact(db, entityId, memoryId, fact) {
20165
20208
  await db.update(agentMemory).set({ fact: trimmed, factHash: computeFactHash(trimmed), updatedAt: /* @__PURE__ */ new Date() }).where(and3(eq5(agentMemory.id, memoryId), eq5(agentMemory.entityId, entityId)));
20166
20209
  return { ok: true };
20167
20210
  }
20211
+ async function updateAgentMemoryImportance(db, entityId, memoryId, importance) {
20212
+ if (!Number.isInteger(importance) || importance < 1 || importance > 5) {
20213
+ return { error: "invalid_importance" };
20214
+ }
20215
+ const row = await loadSourceAndLock(db, entityId, memoryId);
20216
+ if (row === void 0) return { error: "not_found" };
20217
+ if (row.source === "manual") return { error: "not_agent_memory" };
20218
+ if (row.importanceLocked) return { error: "importance_locked" };
20219
+ await db.update(agentMemory).set({ importance, updatedAt: /* @__PURE__ */ new Date() }).where(and3(eq5(agentMemory.id, memoryId), eq5(agentMemory.entityId, entityId)));
20220
+ return { ok: true };
20221
+ }
20168
20222
 
20169
20223
  // ../../packages/memory/src/inject.ts
20170
20224
  var RENDER_OVERHEAD_PER_ENTRY = 20;
@@ -20310,9 +20364,13 @@ function selectMemoriesUnderBudget(memories, maxChars, query) {
20310
20364
  if (aTouched !== bTouched) return bTouched.localeCompare(aTouched);
20311
20365
  return a.id.localeCompare(b.id);
20312
20366
  });
20367
+ return packUnderBudget(sorted, maxChars);
20368
+ }
20369
+ function packUnderBudget(memories, maxChars) {
20370
+ if (maxChars <= 0) return [];
20313
20371
  const picked = [];
20314
20372
  let used = 0;
20315
- for (const m of sorted) {
20373
+ for (const m of memories) {
20316
20374
  const cost = m.fact.length + RENDER_OVERHEAD_PER_ENTRY;
20317
20375
  if (used + cost > maxChars) continue;
20318
20376
  picked.push(m);
@@ -20323,13 +20381,25 @@ function selectMemoriesUnderBudget(memories, maxChars, query) {
20323
20381
  var MAX_CANDIDATES = 200;
20324
20382
  async function selectMemoriesForInjection(db, opts) {
20325
20383
  if (opts.maxChars <= 0) return [];
20326
- const candidates = await db.select().from(agentMemory).where(
20327
- and3(
20328
- eq5(agentMemory.entityId, opts.entityId),
20329
- eq5(agentMemory.archived, false),
20330
- sql18`(${agentMemory.validTo} IS NULL OR ${agentMemory.validTo} > now())`
20331
- )
20332
- ).orderBy(desc(agentMemory.importance), desc(agentMemory.lastAccessedAt)).limit(MAX_CANDIDATES);
20384
+ const baseConditions = and3(
20385
+ eq5(agentMemory.entityId, opts.entityId),
20386
+ eq5(agentMemory.archived, false),
20387
+ sql18`(${agentMemory.validTo} IS NULL OR ${agentMemory.validTo} > now())`
20388
+ );
20389
+ const rawQuery = opts.query ?? "";
20390
+ const tokens = tokenizeQuery(rawQuery);
20391
+ if (tokens.length > 0) {
20392
+ const tsv = sql18`"agent_memory"."search_tsv"`;
20393
+ const tsq = sql18`plainto_tsquery('english', ${rawQuery})`;
20394
+ const candidates2 = await db.select().from(agentMemory).where(baseConditions).orderBy(
20395
+ sql18`ts_rank(${tsv}, ${tsq}) DESC`,
20396
+ desc(agentMemory.importance),
20397
+ desc(agentMemory.lastAccessedAt)
20398
+ ).limit(MAX_CANDIDATES);
20399
+ const memories2 = candidates2.map((row) => rowToMemory(row));
20400
+ return packUnderBudget(memories2, opts.maxChars);
20401
+ }
20402
+ const candidates = await db.select().from(agentMemory).where(baseConditions).orderBy(desc(agentMemory.importance), desc(agentMemory.lastAccessedAt)).limit(MAX_CANDIDATES);
20333
20403
  const memories = candidates.map((row) => rowToMemory(row));
20334
20404
  return selectMemoriesUnderBudget(memories, opts.maxChars, opts.query);
20335
20405
  }
@@ -20584,17 +20654,80 @@ import { z as z25 } from "zod";
20584
20654
  var WebSearchInputSchema = z25.object({
20585
20655
  query: z25.string().min(1).describe("The search query.")
20586
20656
  });
20657
+ var DEGRADED_GUIDANCE = "Free best-effort web search failed (it can be rate-limited or blocked). For reliable results, add a Tavily key (free tier) in the dashboard under Connectors, and I'll use it automatically.";
20658
+ var DDG_ENDPOINT = "https://html.duckduckgo.com/html/";
20659
+ function stripHtml(html) {
20660
+ return html.replace(/<[^>]*>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#x27;/g, "'").replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
20661
+ }
20662
+ function resolveDdgHref(href) {
20663
+ const normalized = href.startsWith("//") ? `https:${href}` : href;
20664
+ const uddg = normalized.match(/[?&]uddg=([^&]+)/);
20665
+ if (uddg?.[1]) {
20666
+ try {
20667
+ return decodeURIComponent(uddg[1]);
20668
+ } catch {
20669
+ }
20670
+ }
20671
+ return normalized;
20672
+ }
20673
+ function parseDuckDuckGoHtml(html) {
20674
+ const results = [];
20675
+ const anchorRe = /<a[^>]*class="[^"]*result__a[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
20676
+ const snippetRe = /<a[^>]*class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/a>/gi;
20677
+ let match;
20678
+ while ((match = anchorRe.exec(html)) !== null) {
20679
+ const href = match[1];
20680
+ const rawTitle = match[2];
20681
+ if (!href || rawTitle === void 0) continue;
20682
+ const title = stripHtml(rawTitle);
20683
+ const url2 = resolveDdgHref(href);
20684
+ if (!title || !url2) continue;
20685
+ snippetRe.lastIndex = anchorRe.lastIndex;
20686
+ const snippetMatch = snippetRe.exec(html);
20687
+ const snippet = snippetMatch?.[1] ? stripHtml(snippetMatch[1]) : "";
20688
+ results.push({ title, url: url2, snippet });
20689
+ }
20690
+ return results;
20691
+ }
20692
+ async function duckDuckGoSearch(query) {
20693
+ let html;
20694
+ try {
20695
+ const res = await fetch(DDG_ENDPOINT, {
20696
+ // The html endpoint blocks GET (returns 202 with an empty challenge page)
20697
+ // but serves 200 + real results for a form POST. A browser-ish UA is also
20698
+ // required (bare requests are refused).
20699
+ method: "POST",
20700
+ headers: {
20701
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36",
20702
+ Accept: "text/html",
20703
+ "Content-Type": "application/x-www-form-urlencoded"
20704
+ },
20705
+ body: "q=" + encodeURIComponent(query)
20706
+ });
20707
+ if (!res.ok) {
20708
+ return { results: [], degraded: true, guidance: DEGRADED_GUIDANCE };
20709
+ }
20710
+ html = await res.text();
20711
+ } catch {
20712
+ return { results: [], degraded: true, guidance: DEGRADED_GUIDANCE };
20713
+ }
20714
+ const results = parseDuckDuckGoHtml(html);
20715
+ if (results.length === 0) {
20716
+ return { results: [], degraded: true, guidance: DEGRADED_GUIDANCE };
20717
+ }
20718
+ return { results };
20719
+ }
20587
20720
  var webSearchTool = {
20588
20721
  name: "web_search",
20589
- description: "Search the web for current information. Use when you need up-to-date data, news, or any facts you are not sure about.",
20722
+ description: "Search the web. Uses your configured Tavily/Firecrawl provider if any, otherwise a free best-effort search.",
20590
20723
  inputSchema: WebSearchInputSchema,
20591
20724
  riskLevel: "read",
20592
- execute: async (_input, _ctx) => {
20593
- const searchUrl = process.env["NODALAI_WEB_SEARCH_URL"];
20594
- if (!searchUrl) {
20595
- throw new WebSearchNotConfiguredError();
20725
+ execute: async (input, ctx) => {
20726
+ if (ctx.searchBackend) {
20727
+ const { results } = await ctx.searchBackend(input.query);
20728
+ return { results };
20596
20729
  }
20597
- throw new WebSearchNotConfiguredError();
20730
+ return duckDuckGoSearch(input.query);
20598
20731
  }
20599
20732
  };
20600
20733
 
@@ -23519,6 +23652,7 @@ var ALWAYS_ON_TOOLS = [
23519
23652
  "search_history",
23520
23653
  "mark_memory_helpful",
23521
23654
  "mark_memory_outdated",
23655
+ "web_search",
23522
23656
  "dashboard_publish",
23523
23657
  "file_read",
23524
23658
  "file_write",
@@ -23536,6 +23670,7 @@ var ALWAYS_ON_TOOL_DOCS = [
23536
23670
  { name: searchHistoryTool.name, description: searchHistoryTool.description },
23537
23671
  { name: markMemoryHelpfulTool.name, description: markMemoryHelpfulTool.description },
23538
23672
  { name: markMemoryOutdatedTool.name, description: markMemoryOutdatedTool.description },
23673
+ { name: webSearchTool.name, description: webSearchTool.description },
23539
23674
  { name: dashboardPublishTool.name, description: dashboardPublishTool.description },
23540
23675
  { name: fileReadTool.name, description: fileReadTool.description },
23541
23676
  { name: fileWriteTool.name, description: fileWriteTool.description },
@@ -27325,7 +27460,7 @@ function decodeBase64Url(encoded) {
27325
27460
  return "";
27326
27461
  }
27327
27462
  }
27328
- function stripHtml(html) {
27463
+ function stripHtml2(html) {
27329
27464
  return html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<[^>]+>/g, " ").replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'").replace(/[ \t]+/g, " ").replace(/\n\s*\n\s*\n/g, "\n\n").trim();
27330
27465
  }
27331
27466
  function decodePartBody(part) {
@@ -27340,7 +27475,7 @@ function extractTextFromPayload(payload) {
27340
27475
  return decodePartBody(payload);
27341
27476
  }
27342
27477
  if (mime.startsWith("text/html")) {
27343
- return stripHtml(decodePartBody(payload));
27478
+ return stripHtml2(decodePartBody(payload));
27344
27479
  }
27345
27480
  if (mime.startsWith("multipart/")) {
27346
27481
  const parts = payload.parts ?? [];
@@ -42081,6 +42216,44 @@ var TOOL_PROVISIONING = {
42081
42216
  },
42082
42217
  encrypt
42083
42218
  };
42219
+ async function resolveSearchBackend(db, agentId) {
42220
+ const rows = await db.select({
42221
+ slug: connectors.slug,
42222
+ apiKey: connectors.apiKey
42223
+ }).from(agentConnectorAssignments).innerJoin(connectors, eq5(connectors.id, agentConnectorAssignments.connectorId)).where(eq5(agentConnectorAssignments.agentId, agentId));
42224
+ for (const slug of ["tavily", "firecrawl"]) {
42225
+ const row = rows.find((r) => r.slug === slug);
42226
+ if (!row?.apiKey) continue;
42227
+ let key;
42228
+ try {
42229
+ key = decrypt(row.apiKey);
42230
+ } catch {
42231
+ continue;
42232
+ }
42233
+ const entry = ADAPTER_REGISTRY[slug];
42234
+ if (!entry) continue;
42235
+ const toolName = slug === "tavily" ? "tavily_search" : "firecrawl_search";
42236
+ const searchTool = entry.toolFactory(key).find((t) => t.name === toolName);
42237
+ if (!searchTool) continue;
42238
+ return async (query) => {
42239
+ const out = await searchTool.execute({ query }, {
42240
+ jobId: "",
42241
+ agentId,
42242
+ entityId: "",
42243
+ db,
42244
+ jobChatId: null
42245
+ });
42246
+ const results = (out.results ?? []).map((r) => ({
42247
+ title: r.title ?? "",
42248
+ url: r.url ?? "",
42249
+ // Tavily → `content`; Firecrawl → `description`.
42250
+ snippet: r.content ?? r.description ?? ""
42251
+ }));
42252
+ return { results };
42253
+ };
42254
+ }
42255
+ return void 0;
42256
+ }
42084
42257
  var DELIVERY_TOOL_NAMES = /* @__PURE__ */ new Set([
42085
42258
  "telegram_send_message",
42086
42259
  "send_image",
@@ -42328,6 +42501,7 @@ async function runJob(jobId, deps, runnerEnv, opts) {
42328
42501
  const scriptAuthorizedSkillSlugs = assignedSkillRows.filter((r) => r.scriptsAuthorized).map((r) => r.slug);
42329
42502
  const fileWritableSkillSlugs = assignedSkillRows.filter((r) => r.filesWritable).map((r) => r.slug);
42330
42503
  const skillStore = skillStoreDir(job.entityId);
42504
+ const searchBackend = await resolveSearchBackend(db, agentRow.id);
42331
42505
  if (!agentRow.llmKeyId) {
42332
42506
  await failJob(db, jobId, "agent_no_llm_configured", runStats(), messages);
42333
42507
  return { status: "failed", error: "agent_no_llm_configured" };
@@ -42647,7 +42821,8 @@ async function runJob(jobId, deps, runnerEnv, opts) {
42647
42821
  assignedSkillSlugs,
42648
42822
  scriptAuthorizedSkillSlugs,
42649
42823
  fileWritableSkillSlugs,
42650
- provisioning: TOOL_PROVISIONING
42824
+ provisioning: TOOL_PROVISIONING,
42825
+ searchBackend
42651
42826
  },
42652
42827
  {
42653
42828
  approvalRules: resumeApprovalRules,
@@ -43059,7 +43234,8 @@ async function runJob(jobId, deps, runnerEnv, opts) {
43059
43234
  assignedSkillSlugs,
43060
43235
  scriptAuthorizedSkillSlugs,
43061
43236
  fileWritableSkillSlugs,
43062
- provisioning: TOOL_PROVISIONING
43237
+ provisioning: TOOL_PROVISIONING,
43238
+ searchBackend
43063
43239
  };
43064
43240
  const sharedToolOpts = {
43065
43241
  approvalRules: approvalRuleList,
@@ -43190,7 +43366,8 @@ async function runJob(jobId, deps, runnerEnv, opts) {
43190
43366
  assignedSkillSlugs,
43191
43367
  scriptAuthorizedSkillSlugs,
43192
43368
  fileWritableSkillSlugs,
43193
- provisioning: TOOL_PROVISIONING
43369
+ provisioning: TOOL_PROVISIONING,
43370
+ searchBackend
43194
43371
  },
43195
43372
  {
43196
43373
  approvalRules: approvalRuleList,
@@ -44495,10 +44672,15 @@ var EditMemoryArgs = z113.object({
44495
44672
  var ArchiveMemoryArgs = z113.object({
44496
44673
  memoryId: z113.string().uuid("memoryId must be a valid UUID.")
44497
44674
  });
44675
+ var SetImportanceArgs = z113.object({
44676
+ memoryId: z113.string().uuid("memoryId must be a valid UUID."),
44677
+ importance: z113.number().int().min(1).max(5),
44678
+ reason: z113.string().min(1).max(200)
44679
+ });
44498
44680
  function buildSystemPrompt2(candidateList) {
44499
44681
  return `You are the MEMORY CURATOR for this entity. Your job is to keep the durable-fact memory SMALL, DENSE, and NON-REDUNDANT, so the limited injection budget is spent on signal.
44500
44682
 
44501
- CANDIDATE FACTS (id, source, importance, length, text):
44683
+ CANDIDATE FACTS (id, source, importance, access_count, length, text):
44502
44684
  ${candidateList}
44503
44685
 
44504
44686
  WHAT A GOOD FACT IS: ONE short, durable statement (a preference, an ID, a convention, a learned rule) \u2014 typically under ${HEALTHY_FACT_CHARS} characters. NOT a document, a raw dump, or a session log.
@@ -44507,12 +44689,14 @@ ACTIONS:
44507
44689
  1. DISTILL oversized facts \u2014 if a fact is a big blob (a pasted workflow/JSON, a long log, a transcript), call edit_memory to replace it with just the key statement + a pointer to where the artifact actually lives (e.g. "... saved in workspace file workflows/X.json"). Keep the SIGNAL, drop the bulk.
44508
44690
  2. MERGE near-duplicates \u2014 when several facts say the same thing, edit_memory ONE of them to the best consolidated wording, then archive_memory the others.
44509
44691
  3. ARCHIVE the clearly obsolete or contradictory.
44692
+ 4. RE-SCORE importance \u2014 a fact's initial importance is just the authoring agent's one-shot estimate. access_count is PROVEN usage, and it is more reliable than that estimate. Call set_importance to RAISE the importance of a durable fact that is accessed often (it clearly matters), and to LOWER the importance of a fact that has never been used despite being around a while (it probably doesn't). Give a short reason.
44510
44693
 
44511
44694
  HARD RULES:
44512
- - You may ONLY edit/archive AGENT-authored facts. The tools REFUSE source='manual' (user-entered) \u2014 do NOT attempt them, and do not retry a refused call.
44695
+ - You may ONLY edit/archive/re-score AGENT-authored facts. The tools REFUSE source='manual' (user-entered) \u2014 do NOT attempt them, and do not retry a refused call.
44513
44696
  - Archiving is the maximum action \u2014 it is reversible; never invent or fabricate facts.
44514
44697
  - When a fact is already a clean short statement, LEAVE IT. A no-op pass is correct and common on a tidy pool.
44515
- - Do NOT merge facts that are merely related but distinct \u2014 only true duplicates.`;
44698
+ - Do NOT merge facts that are merely related but distinct \u2014 only true duplicates.
44699
+ - Do NOT re-score every fact out of habit \u2014 only when access_count clearly disagrees with the current importance.`;
44516
44700
  }
44517
44701
  function renderCandidates(facts) {
44518
44702
  if (facts.length === 0) return "(none)";
@@ -44520,7 +44704,7 @@ function renderCandidates(facts) {
44520
44704
  const len = f.fact.length;
44521
44705
  const oversized = len > HEALTHY_FACT_CHARS ? " OVERSIZED" : "";
44522
44706
  const text22 = len > 200 ? `${f.fact.slice(0, 200)}\u2026` : f.fact;
44523
- return `- id=${f.id} source=${f.source ?? "agent"} importance=${f.importance ?? 3} len=${len}${oversized}
44707
+ return `- id=${f.id} source=${f.source ?? "agent"} importance=${f.importance ?? 3} access_count=${f.accessCount ?? 0} len=${len}${oversized}
44524
44708
  ${text22.replace(/\s+/g, " ")}`;
44525
44709
  }).join("\n");
44526
44710
  }
@@ -44529,9 +44713,10 @@ async function runMemoryCuration(db, entityId, maxTurns, reflectionModel) {
44529
44713
  id: agentMemory.id,
44530
44714
  fact: agentMemory.fact,
44531
44715
  source: agentMemory.source,
44532
- importance: agentMemory.importance
44716
+ importance: agentMemory.importance,
44717
+ accessCount: agentMemory.accessCount
44533
44718
  }).from(agentMemory).where(and3(eq5(agentMemory.entityId, entityId), eq5(agentMemory.archived, false))).limit(MAX_CANDIDATES2);
44534
- if (candidates.length === 0) return { edited: 0, archived: 0, turns: 0 };
44719
+ if (candidates.length === 0) return { edited: 0, archived: 0, rescored: 0, turns: 0 };
44535
44720
  const agentRows = await db.select({
44536
44721
  llmKeyId: agents.llmKeyId,
44537
44722
  fallbackChain: agents.fallbackChain,
@@ -44554,7 +44739,7 @@ async function runMemoryCuration(db, entityId, maxTurns, reflectionModel) {
44554
44739
  }
44555
44740
  if (!resolved?.ok) {
44556
44741
  console.warn(`${TRACE} no LLM client for entity ${entityId}, skipping`);
44557
- return { edited: 0, archived: 0, turns: 0 };
44742
+ return { edited: 0, archived: 0, rescored: 0, turns: 0 };
44558
44743
  }
44559
44744
  const llmClient = resolved.client;
44560
44745
  const systemPrompt = buildSystemPrompt2(renderCandidates(candidates));
@@ -44572,10 +44757,15 @@ async function runMemoryCuration(db, entityId, maxTurns, reflectionModel) {
44572
44757
  archive_memory: {
44573
44758
  description: "Soft-archive a redundant/obsolete agent-authored fact (reversible). Provide the memory UUID. Refuses user-entered (manual) facts.",
44574
44759
  inputSchema: ArchiveMemoryArgs
44760
+ },
44761
+ set_importance: {
44762
+ description: "Re-score a fact's importance (1-5) from REAL usage (access_count) + relevance. Raise for frequently-accessed durable facts; lower for never-used ones. Refuses user-entered (manual) facts.",
44763
+ inputSchema: SetImportanceArgs
44575
44764
  }
44576
44765
  };
44577
44766
  let edited = 0;
44578
44767
  let archived = 0;
44768
+ let rescored = 0;
44579
44769
  let turns = 0;
44580
44770
  console.warn(`${TRACE} start`, { entityId, candidateCount: candidates.length });
44581
44771
  for (let turn = 0; turn < maxTurns; turn += 1) {
@@ -44633,6 +44823,24 @@ async function runMemoryCuration(db, entityId, maxTurns, reflectionModel) {
44633
44823
  outcome = `archived memory ${parsed.data.memoryId}`;
44634
44824
  }
44635
44825
  }
44826
+ } else if (tc.toolName === "set_importance") {
44827
+ const parsed = SetImportanceArgs.safeParse(tc.input);
44828
+ if (!parsed.success) {
44829
+ outcome = `error: invalid_input: ${parsed.error.message}`;
44830
+ } else {
44831
+ const res = await updateAgentMemoryImportance(
44832
+ db,
44833
+ entityId,
44834
+ parsed.data.memoryId,
44835
+ parsed.data.importance
44836
+ );
44837
+ if ("error" in res) {
44838
+ outcome = res.error === "not_agent_memory" ? `error: memory ${parsed.data.memoryId} is user-entered and cannot be re-scored by the curator.` : res.error === "invalid_importance" ? `error: importance must be an integer 1-5.` : res.error === "importance_locked" ? `error: memory ${parsed.data.memoryId} importance is user-locked and cannot be re-scored.` : `error: memory ${parsed.data.memoryId} not found in this entity.`;
44839
+ } else {
44840
+ rescored += 1;
44841
+ outcome = `set importance of memory ${parsed.data.memoryId} to ${parsed.data.importance} (${parsed.data.reason})`;
44842
+ }
44843
+ }
44636
44844
  } else {
44637
44845
  outcome = `error: unknown tool ${tc.toolName}`;
44638
44846
  }
@@ -44645,8 +44853,8 @@ async function runMemoryCuration(db, entityId, maxTurns, reflectionModel) {
44645
44853
  }
44646
44854
  messages.push({ role: "tool", content: resultParts });
44647
44855
  }
44648
- console.warn(`${TRACE} done`, { entityId, edited, archived, turns });
44649
- return { edited, archived, turns };
44856
+ console.warn(`${TRACE} done`, { entityId, edited, archived, rescored, turns });
44857
+ return { edited, archived, rescored, turns };
44650
44858
  }
44651
44859
 
44652
44860
  // src/cron/run-curator.ts
@@ -44661,6 +44869,11 @@ var SAFE_LIFECYCLE_DEFAULTS = {
44661
44869
  CURATOR_MEMORY_IMPORTANCE_MAX: 2,
44662
44870
  CURATOR_MEMORY_MIN: 8,
44663
44871
  REFLECTION_ENABLED: "false",
44872
+ // Conservative fallback for environments without DATABASE_URL (test envs) —
44873
+ // mirrors REFLECTION_ENABLED's safe default. The REAL production default
44874
+ // (per-entity decides, ON since entities.memory_curation_enabled defaults
44875
+ // true) comes from the zod schema in env.ts, not from this fallback.
44876
+ MEMORY_CURATION_ENABLED: "false",
44664
44877
  REFLECTION_MODEL: void 0
44665
44878
  };
44666
44879
  function resolveCuratorEnv(runnerEnv) {
@@ -44677,6 +44890,7 @@ function resolveCuratorEnv(runnerEnv) {
44677
44890
  CURATOR_MEMORY_IMPORTANCE_MAX: env.CURATOR_MEMORY_IMPORTANCE_MAX,
44678
44891
  CURATOR_MEMORY_MIN: env.CURATOR_MEMORY_MIN,
44679
44892
  REFLECTION_ENABLED: env.REFLECTION_ENABLED,
44893
+ MEMORY_CURATION_ENABLED: env.MEMORY_CURATION_ENABLED,
44680
44894
  REFLECTION_MODEL: env.REFLECTION_MODEL
44681
44895
  };
44682
44896
  } catch {
@@ -44693,7 +44907,26 @@ async function runCuratorTick(db, _deps, runnerEnv) {
44693
44907
  staleDays: e.CURATOR_MEMORY_STALE_DAYS,
44694
44908
  importanceMax: e.CURATOR_MEMORY_IMPORTANCE_MAX
44695
44909
  });
44696
- if (e.REFLECTION_ENABLED === "false") {
44910
+ const intervalMs = e.CURATOR_INTERVAL_DAYS * 24 * 60 * 60 * 1e3;
44911
+ const intervalCutoff = new Date(Date.now() - intervalMs);
44912
+ const now = /* @__PURE__ */ new Date();
44913
+ const skillSet = /* @__PURE__ */ new Set();
44914
+ if (e.REFLECTION_ENABLED !== "false") {
44915
+ const skillCandidates = await db.select({ entityId: agentSkills.entityId }).from(agentSkills).innerJoin(entities, eq5(entities.id, agentSkills.entityId)).where(
44916
+ and3(
44917
+ eq5(agentSkills.createdBy, "agent"),
44918
+ eq5(agentSkills.state, "active"),
44919
+ eq5(entities.reflectionEnabled, true)
44920
+ )
44921
+ ).groupBy(agentSkills.entityId).having(sql18`count(${agentSkills.id}) >= ${e.CURATOR_MIN_SKILLS}`);
44922
+ for (const c of skillCandidates) if (c.entityId) skillSet.add(c.entityId);
44923
+ }
44924
+ const memorySet = /* @__PURE__ */ new Set();
44925
+ if (e.MEMORY_CURATION_ENABLED !== "false") {
44926
+ const memoryCandidates = await db.select({ entityId: agentMemory.entityId }).from(agentMemory).innerJoin(entities, eq5(entities.id, agentMemory.entityId)).where(and3(eq5(agentMemory.archived, false), eq5(entities.memoryCurationEnabled, true))).groupBy(agentMemory.entityId).having(sql18`count(${agentMemory.id}) >= ${e.CURATOR_MEMORY_MIN}`);
44927
+ for (const c of memoryCandidates) if (c.entityId) memorySet.add(c.entityId);
44928
+ }
44929
+ if (skillSet.size === 0 && memorySet.size === 0) {
44697
44930
  return {
44698
44931
  staled: lifecycle.staled,
44699
44932
  archived: lifecycle.archived,
@@ -44704,21 +44937,6 @@ async function runCuratorTick(db, _deps, runnerEnv) {
44704
44937
  memoryCurationRan: 0
44705
44938
  };
44706
44939
  }
44707
- const intervalMs = e.CURATOR_INTERVAL_DAYS * 24 * 60 * 60 * 1e3;
44708
- const intervalCutoff = new Date(Date.now() - intervalMs);
44709
- const now = /* @__PURE__ */ new Date();
44710
- const skillCandidates = await db.select({ entityId: agentSkills.entityId }).from(agentSkills).innerJoin(entities, eq5(entities.id, agentSkills.entityId)).where(
44711
- and3(
44712
- eq5(agentSkills.createdBy, "agent"),
44713
- eq5(agentSkills.state, "active"),
44714
- eq5(entities.reflectionEnabled, true)
44715
- )
44716
- ).groupBy(agentSkills.entityId).having(sql18`count(${agentSkills.id}) >= ${e.CURATOR_MIN_SKILLS}`);
44717
- const memoryCandidates = await db.select({ entityId: agentMemory.entityId }).from(agentMemory).innerJoin(entities, eq5(entities.id, agentMemory.entityId)).where(and3(eq5(agentMemory.archived, false), eq5(entities.reflectionEnabled, true))).groupBy(agentMemory.entityId).having(sql18`count(${agentMemory.id}) >= ${e.CURATOR_MEMORY_MIN}`);
44718
- const skillSet = /* @__PURE__ */ new Set();
44719
- for (const c of skillCandidates) if (c.entityId) skillSet.add(c.entityId);
44720
- const memorySet = /* @__PURE__ */ new Set();
44721
- for (const c of memoryCandidates) if (c.entityId) memorySet.add(c.entityId);
44722
44940
  let consolidationDeferred = 0;
44723
44941
  let consolidationRan = 0;
44724
44942
  let memoryCurationRan = 0;
@@ -1 +1 @@
1
- vB-0ke6pDH6vYyme7Pz5G
1
+ KFac7DQ7gdp9XPQ2bnb-2
@@ -12,8 +12,8 @@
12
12
  "/(dashboard)/agents/[id]/edit/page": "/agents/[id]/edit",
13
13
  "/(dashboard)/agents/[id]/telegram/page": "/agents/[id]/telegram",
14
14
  "/(dashboard)/agents/page": "/agents",
15
- "/(dashboard)/approvals/page": "/approvals",
16
15
  "/(dashboard)/automations/page": "/automations",
16
+ "/(dashboard)/approvals/page": "/approvals",
17
17
  "/(dashboard)/chat/page": "/chat",
18
18
  "/(dashboard)/connectors/page": "/connectors",
19
19
  "/(dashboard)/credentials/page": "/credentials",
@@ -24,9 +24,9 @@
24
24
  "/(dashboard)/logs/page": "/logs",
25
25
  "/(dashboard)/mcp/page": "/mcp",
26
26
  "/(dashboard)/memories/page": "/memories",
27
+ "/(dashboard)/settings/page": "/settings",
27
28
  "/(dashboard)/settings/root-context/page": "/settings/root-context",
28
29
  "/(dashboard)/page": "/",
29
- "/(dashboard)/settings/page": "/settings",
30
30
  "/(dashboard)/skills/[id]/edit/page": "/skills/[id]/edit",
31
31
  "/(dashboard)/skills/page": "/skills"
32
32
  }
@@ -4,8 +4,8 @@
4
4
  ],
5
5
  "devFiles": [],
6
6
  "lowPriorityFiles": [
7
- "static/vB-0ke6pDH6vYyme7Pz5G/_buildManifest.js",
8
- "static/vB-0ke6pDH6vYyme7Pz5G/_ssgManifest.js"
7
+ "static/KFac7DQ7gdp9XPQ2bnb-2/_buildManifest.js",
8
+ "static/KFac7DQ7gdp9XPQ2bnb-2/_ssgManifest.js"
9
9
  ],
10
10
  "rootMainFiles": [
11
11
  "static/chunks/webpack-3741bf0a7636d65e.js",
@@ -54,8 +54,8 @@
54
54
  "dynamicRoutes": {},
55
55
  "notFoundRoutes": [],
56
56
  "preview": {
57
- "previewModeId": "3ddad284aca041171a552f29c79b2200",
58
- "previewModeSigningKey": "f2ff4ccd9469bedec6018f452f10feb96216a89db18329912414b8fc3c4cb1af",
59
- "previewModeEncryptionKey": "9a716beb1723a56c4e5ebee6c2b7f7efc7dad2855ea8eca4ff499e88c104f0c1"
57
+ "previewModeId": "9ca9c6c94be2ac6b133cc8186cc3e575",
58
+ "previewModeSigningKey": "e0be6b0a50047a39c91cb1f3c0c956afeeca98f59c75c182395022d820c862a6",
59
+ "previewModeEncryptionKey": "8eb9a91dc33886853caf249132d193b1db18b4ad8b66413c2343f5f4a4c31d37"
60
60
  }
61
61
  }