neoagent 3.0.1-beta.29 → 3.0.1-beta.30

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/docs/billing.md CHANGED
@@ -61,6 +61,11 @@ Token limits are written directly to the user account when a subscription
61
61
  becomes active or is updated via webhook. The existing rate-limit enforcement
62
62
  in the runtime reads them with no additional configuration.
63
63
 
64
+ Token limits gate interactive run starts. Background task executions are not
65
+ blocked by the internal token admission check, so scheduled and integration
66
+ tasks can still run while a user is over quota. Their token usage is still
67
+ recorded and included in account and admin usage snapshots.
68
+
64
69
  ### Free plan
65
70
 
66
71
  Create a plan with **Price = 0** to serve as the default for new users. If a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "3.0.1-beta.29",
3
+ "version": "3.0.1-beta.30",
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",
@@ -1 +1 @@
1
- 8b097147c5ec8ac3bee082f22bd5d248
1
+ 400b929549327d2b7624182c525fe34e
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"a10d8ac38de835021c8d2f920dbf50a920ccc0
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "1039759445" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "74968846" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -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("mr0924tg-6e8948b").length!==0&&r.b}if(r){r=s.d
138722
+ r=B.b.t("mr09cv28-5adc70e").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("mr0924tg-6e8948b").length===0||s.a!=null)return
145018
+ if(B.b.t("mr09cv28-5adc70e").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,"mr0924tg-6e8948b")){s=1
145036
+ if(J.bd(j)===0||J.f(j,"mr09cv28-5adc70e")){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("mr0924tg-6e8948b").length===0||n.c){s=1
145053
+ s=p}for(;;)switch(s){case 0:if(B.b.t("mr09cv28-5adc70e").length===0||n.c){s=1
145054
145054
  break}n.c=!0
145055
145055
  n.E()
145056
145056
  p=4
@@ -571,7 +571,9 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
571
571
  let deliverableWorkflow = null;
572
572
  const timelineService = app?.locals?.timelineService || null;
573
573
 
574
- const { releaseReservation } = enforceRateLimits(userId);
574
+ const { releaseReservation } = enforceRateLimits(userId, {
575
+ bypass: options.bypassUserRateLimits === true,
576
+ });
575
577
 
576
578
  try {
577
579
  const historyWindow = Math.max(
@@ -9,12 +9,7 @@ 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;
12
+ const MAX_RUN_RESERVATION_TOKENS = 100_000;
18
13
 
19
14
  const WINDOWS = {
20
15
  fourHour: {
@@ -142,7 +137,28 @@ function getRateLimitSnapshot(userId, { includeReservations = false } = {}) {
142
137
  };
143
138
  }
144
139
 
145
- function enforceRateLimits(userId) {
140
+ function noopReleaseReservation() {}
141
+
142
+ function calculateReservation(limits) {
143
+ const finiteLimits = [limits.fourHour, limits.weekly]
144
+ .filter((limit) => Number.isFinite(limit) && limit > 0);
145
+ if (finiteLimits.length === 0) return 1;
146
+ const minFiniteLimit = Math.min(...finiteLimits);
147
+ return Math.min(
148
+ MAX_RUN_RESERVATION_TOKENS,
149
+ Math.max(1, Math.floor(minFiniteLimit * 0.1)),
150
+ );
151
+ }
152
+
153
+ function enforceRateLimits(userId, options = {}) {
154
+ if (options.bypass === true) {
155
+ return {
156
+ snapshot: getRateLimitSnapshot(userId, { includeReservations: false }),
157
+ releaseReservation: noopReleaseReservation,
158
+ bypassed: true,
159
+ };
160
+ }
161
+
146
162
  const snapshot = getRateLimitSnapshot(userId, { includeReservations: true });
147
163
  if (snapshot.reached.fourHour) {
148
164
  throw new RateLimitExceededError('fourHour', snapshot);
@@ -150,23 +166,10 @@ function enforceRateLimits(userId) {
150
166
  if (snapshot.reached.weekly) {
151
167
  throw new RateLimitExceededError('weekly', snapshot);
152
168
  }
153
- // Reserve a placeholder so concurrent starts see this run as in-flight.
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.
169
+ // Reserve a bounded placeholder so concurrent starts see this run as
170
+ // in-flight without letting one run consume the user's entire budget.
159
171
  const key = String(userId);
160
- const limits = snapshot.limits;
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));
172
+ const reserve = calculateReservation(snapshot.limits);
170
173
  _reservations.set(key, (_reservations.get(key) || 0) + reserve);
171
174
  return { snapshot, releaseReservation: () => releaseReservation(userId, reserve) };
172
175
  }
@@ -185,6 +188,7 @@ function releaseReservation(userId, amount) {
185
188
  module.exports = {
186
189
  DEFAULT_RATE_LIMIT_4H,
187
190
  DEFAULT_RATE_LIMIT_WEEKLY,
191
+ MAX_RUN_RESERVATION_TOKENS,
188
192
  RateLimitExceededError,
189
193
  configuredDefaultLimits,
190
194
  enforceRateLimits,
@@ -567,11 +567,12 @@ class TaskRuntime {
567
567
  const finalPrompt = basePrompt + recoveryNote;
568
568
  const runOptions = {
569
569
  triggerType: task.trigger_type || 'schedule',
570
- triggerSource: task.trigger_type || 'schedule',
570
+ triggerSource: executionMeta.triggerSource || task.trigger_type || 'schedule',
571
571
  agentId,
572
572
  app: this.app,
573
573
  conversationId,
574
574
  taskId,
575
+ bypassUserRateLimits: true,
575
576
  deliveryState,
576
577
  allowMultipleProactiveMessages: normalizedConfig.allowMultipleMessages === true || normalizedConfig.allow_multiple_messages === true,
577
578
  stageProactiveMessages: true,