neoagent 3.0.1-beta.26 → 3.0.1-beta.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "3.0.1-beta.26",
3
+ "version": "3.0.1-beta.28",
4
4
  "description": "Self-hosted AI agent for long-running tasks, automation, messaging, device control, and local memory",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "server/index.js",
@@ -2016,7 +2016,8 @@ function backfillTaskTriggers() {
2016
2016
  .all();
2017
2017
  const update = db.prepare(
2018
2018
  `UPDATE scheduled_tasks
2019
- SET trigger_type = ?, trigger_config = ?, execution_mode = COALESCE(NULLIF(execution_mode, ''), 'prompt')
2019
+ SET trigger_type = ?, trigger_config = ?, cron_expression = ?, run_at = ?,
2020
+ execution_mode = COALESCE(NULLIF(execution_mode, ''), 'prompt')
2020
2021
  WHERE id = ?`,
2021
2022
  );
2022
2023
  const tx = db.transaction(() => {
@@ -2028,17 +2029,39 @@ function backfillTaskTriggers() {
2028
2029
  } catch {
2029
2030
  parsedConfig = {};
2030
2031
  }
2031
- const hasConfig = parsedConfig && typeof parsedConfig === 'object' && !Array.isArray(parsedConfig) && Object.keys(parsedConfig).length > 0;
2032
- if (triggerType !== 'schedule' && hasConfig) {
2033
- update.run(triggerType, JSON.stringify(parsedConfig), row.id);
2032
+ if (!parsedConfig || typeof parsedConfig !== 'object' || Array.isArray(parsedConfig)) {
2033
+ parsedConfig = {};
2034
+ }
2035
+
2036
+ // Non-schedule triggers keep their own config untouched.
2037
+ if (triggerType !== 'schedule') {
2038
+ if (Object.keys(parsedConfig).length > 0) {
2039
+ update.run(triggerType, JSON.stringify(parsedConfig), row.cron_expression, row.run_at, row.id);
2040
+ }
2034
2041
  continue;
2035
2042
  }
2036
2043
 
2037
- const mode = row.one_time ? 'one_time' : 'recurring';
2038
- const config = row.one_time
2039
- ? { mode, runAt: row.run_at || null }
2040
- : { mode, cronExpression: row.cron_expression || null };
2041
- update.run('schedule', JSON.stringify(config), row.id);
2044
+ // Schedule triggers: reconcile the structured trigger_config with the
2045
+ // legacy cron_expression/run_at columns WITHOUT discarding a value that
2046
+ // exists in only one place. A prior version rebuilt trigger_config from
2047
+ // the legacy columns unconditionally, which blanked cronExpression for
2048
+ // tasks that carried it only in trigger_config and silently stopped them
2049
+ // from ever being scheduled.
2050
+ const cronExpression = String(parsedConfig.cronExpression || row.cron_expression || '').trim() || null;
2051
+ const runAt = parsedConfig.runAt || row.run_at || null;
2052
+ const isOneTime = cronExpression
2053
+ ? false
2054
+ : (Boolean(row.one_time) || parsedConfig.mode === 'one_time');
2055
+ const config = isOneTime
2056
+ ? { mode: 'one_time', runAt }
2057
+ : { mode: 'recurring', cronExpression };
2058
+ update.run(
2059
+ 'schedule',
2060
+ JSON.stringify(config),
2061
+ isOneTime ? null : cronExpression,
2062
+ isOneTime ? runAt : null,
2063
+ row.id,
2064
+ );
2042
2065
  }
2043
2066
  });
2044
2067
  tx();
@@ -1 +1 @@
1
- d0f10aa867a5cbedc987a0383d9bde57
1
+ bc9ff412e67a5da2a54969c0335a544f
@@ -33,10 +33,10 @@ addEventListener("message", eventListener);
33
33
  if (!window._flutter) {
34
34
  window._flutter = {};
35
35
  }
36
- _flutter.buildConfig = {"engineRevision":"a4ce257c68517c1410f4b48ac9852ab5642a3f8d","builds":[{"compileTarget":"dart2js","renderer":"canvaskit","mainJsPath":"main.dart.js"},{}]};
36
+ _flutter.buildConfig = {"engineRevision":"a10d8ac38de835021c8d2f920dbf50a920ccc030","builds":[{"compileTarget":"dart2js","renderer":"canvaskit","mainJsPath":"main.dart.js"},{}]};
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "393692328" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "2180136218" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -41215,7 +41215,7 @@ $S:4}
41215
41215
  A.bhj.prototype={
41216
41216
  $1(a){var s=A.fd().b
41217
41217
  s=s==null?null:s.canvasKitBaseUrl
41218
- return(s==null?"https://www.gstatic.com/flutter-canvaskit/a4ce257c68517c1410f4b48ac9852ab5642a3f8d/":s)+a},
41218
+ return(s==null?"https://www.gstatic.com/flutter-canvaskit/a10d8ac38de835021c8d2f920dbf50a920ccc030/":s)+a},
41219
41219
  $S:39}
41220
41220
  A.a_D.prototype={
41221
41221
  gH(a){var s=this.a
@@ -138719,7 +138719,7 @@ r===$&&A.b()
138719
138719
  p.push(A.iU(q,A.ju(!1,new A.Y(B.vr,A.cN(new A.cA(B.jU,new A.a94(r,q),q),q,q),q),!1,B.I,!0),q,q,0,0,0,q))}r=!1
138720
138720
  if(!s.ay)if(!s.ch){r=s.e
138721
138721
  r===$&&A.b()
138722
- r=B.b.t("mqro5gaw-044e21a").length!==0&&r.b}if(r){r=s.d
138722
+ r=B.b.t("mqznd4bv-b094a16").length!==0&&r.b}if(r){r=s.d
138723
138723
  r===$&&A.b()
138724
138724
  r=r.aD&&!r.av?84:0
138725
138725
  s=s.e
@@ -145015,7 +145015,7 @@ $S:0}
145015
145015
  A.a0g.prototype={}
145016
145016
  A.U0.prototype={
145017
145017
  nm(a){var s=this
145018
- if(B.b.t("mqro5gaw-044e21a").length===0||s.a!=null)return
145018
+ if(B.b.t("mqznd4bv-b094a16").length===0||s.a!=null)return
145019
145019
  s.Bg()
145020
145020
  s.a=A.m4(B.Td,new A.bgn(s))},
145021
145021
  Bg(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
@@ -145033,7 +145033,7 @@ if(!t.f.b(k)){s=1
145033
145033
  break}i=J.a2(k,"buildId")
145034
145034
  h=i==null?null:B.b.t(J.p(i))
145035
145035
  j=h==null?"":h
145036
- if(J.bd(j)===0||J.f(j,"mqro5gaw-044e21a")){s=1
145036
+ if(J.bd(j)===0||J.f(j,"mqznd4bv-b094a16")){s=1
145037
145037
  break}n.b=!0
145038
145038
  n.E()
145039
145039
  p=2
@@ -145050,7 +145050,7 @@ case 2:return A.i(o.at(-1),r)}})
145050
145050
  return A.k($async$Bg,r)},
145051
145051
  vV(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
145052
145052
  var $async$vV=A.h(function(a2,a3){if(a2===1){o.push(a3)
145053
- s=p}for(;;)switch(s){case 0:if(B.b.t("mqro5gaw-044e21a").length===0||n.c){s=1
145053
+ s=p}for(;;)switch(s){case 0:if(B.b.t("mqznd4bv-b094a16").length===0||n.c){s=1
145054
145054
  break}n.c=!0
145055
145055
  n.E()
145056
145056
  p=4
@@ -9,6 +9,13 @@ const _reservations = new Map();
9
9
  const DEFAULT_RATE_LIMIT_4H = 2_500_000;
10
10
  const DEFAULT_RATE_LIMIT_WEEKLY = 10_000_000;
11
11
 
12
+ // Generous upper bound on a single run's token cost. Used to size the in-flight
13
+ // reservation that keeps concurrent run starts from collectively bypassing the
14
+ // per-user budget. It must NOT be the full limit — doing so would reserve the
15
+ // entire budget for one run and reject any second concurrent run for the same
16
+ // user (e.g. a scheduled task firing in the same minute as another).
17
+ const DEFAULT_RUN_RESERVATION_TOKENS = 500_000;
18
+
12
19
  const WINDOWS = {
13
20
  fourHour: {
14
21
  durationMs: 4 * 60 * 60 * 1000,
@@ -144,10 +151,22 @@ function enforceRateLimits(userId) {
144
151
  throw new RateLimitExceededError('weekly', snapshot);
145
152
  }
146
153
  // Reserve a placeholder so concurrent starts see this run as in-flight.
147
- // The reservation is released (or reconciled) when the run completes.
154
+ // The reservation is released (or reconciled) when the run completes. Size it
155
+ // to a realistic per-run estimate — never the full limit — so concurrent runs
156
+ // for the same user are admitted normally and only a genuinely near-budget
157
+ // user gets throttled. Clamp so the reservation can never by itself exceed a
158
+ // (possibly small/custom) limit.
148
159
  const key = String(userId);
149
160
  const limits = snapshot.limits;
150
- const reserve = Math.max(limits.fourHour || 0, limits.weekly || 0, 1);
161
+ const estimate = parsePositiveInteger(
162
+ process.env.NEOAGENT_RUN_RESERVATION_TOKENS,
163
+ DEFAULT_RUN_RESERVATION_TOKENS,
164
+ );
165
+ const cap = Math.min(
166
+ limits.fourHour == null ? Infinity : limits.fourHour,
167
+ limits.weekly == null ? Infinity : limits.weekly,
168
+ );
169
+ const reserve = Math.max(1, Math.min(estimate, Number.isFinite(cap) ? cap : estimate));
151
170
  _reservations.set(key, (_reservations.get(key) || 0) + reserve);
152
171
  return { snapshot, releaseReservation: () => releaseReservation(userId, reserve) };
153
172
  }
@@ -381,9 +381,18 @@ class TaskRuntime {
381
381
  if (!task.enabled) return;
382
382
  if ((task.trigger_type || 'schedule') !== 'schedule') return;
383
383
  const triggerConfig = this._normalizeJson(task.trigger_config);
384
- if (triggerConfig.mode === 'one_time') return;
385
- const cronExpression = String(triggerConfig.cronExpression || '').trim();
386
- if (!cronExpression) return;
384
+ // Resolve the cron expression from the structured trigger_config, falling
385
+ // back to the legacy cron_expression column. An older migration could leave
386
+ // the expression in only one of the two places; as long as either still
387
+ // holds it, the task must keep being scheduled.
388
+ const cronExpression = String(
389
+ triggerConfig.cronExpression || task.cron_expression || '',
390
+ ).trim();
391
+ // One-time runs are driven by the one-time poller, not node-cron. Only skip
392
+ // here when there is genuinely no recurring cron expression to honor.
393
+ if (!cronExpression) {
394
+ return;
395
+ }
387
396
  const job = this.cron.schedule(cronExpression, async () => {
388
397
  try {
389
398
  await this._executeTask(task.id, task.user_id, {