neoagent 3.0.1-beta.8 → 3.0.1-beta.9

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.8",
3
+ "version": "3.0.1-beta.9",
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",
@@ -318,3 +318,81 @@ button:disabled,
318
318
  border: 1px solid rgba(116, 192, 124, 0.25);
319
319
  color: var(--success);
320
320
  }
321
+
322
+ /* ── Form helpers (used in dynamic modals) ───────────────────────────────── */
323
+ .form-label {
324
+ display: block;
325
+ font-size: 11px;
326
+ font-weight: 600;
327
+ letter-spacing: 0.04em;
328
+ color: var(--text-muted);
329
+ text-transform: uppercase;
330
+ margin-bottom: 6px;
331
+ }
332
+
333
+ .form-label small {
334
+ font-size: 10px;
335
+ font-weight: 400;
336
+ text-transform: none;
337
+ letter-spacing: 0;
338
+ opacity: 0.75;
339
+ }
340
+
341
+ .form-input {
342
+ width: 100%;
343
+ background: var(--bg-input);
344
+ border: 1px solid var(--border-light);
345
+ border-radius: var(--radius-sm);
346
+ padding: 9px 12px;
347
+ color: var(--text);
348
+ font-family: var(--font-sans);
349
+ font-size: 13px;
350
+ outline: none;
351
+ transition: border-color 0.15s;
352
+ -webkit-appearance: none;
353
+ }
354
+
355
+ .form-input:focus {
356
+ border-color: var(--accent);
357
+ box-shadow: 0 0 0 3px var(--accent-muted);
358
+ }
359
+
360
+ .form-input:disabled {
361
+ opacity: 0.5;
362
+ cursor: default;
363
+ }
364
+
365
+ /* ── Small button variant ────────────────────────────────────────────────── */
366
+ .btn-sm {
367
+ padding: 5px 10px;
368
+ font-size: 12px;
369
+ }
370
+
371
+ /* ── Generic data table (billing, etc.) ──────────────────────────────────── */
372
+ .data-table {
373
+ width: 100%;
374
+ border-collapse: collapse;
375
+ font-size: 13px;
376
+ }
377
+
378
+ .data-table th {
379
+ text-align: left;
380
+ font-size: 10px;
381
+ font-weight: 600;
382
+ text-transform: uppercase;
383
+ letter-spacing: 0.07em;
384
+ color: var(--text-muted);
385
+ padding: 6px 10px;
386
+ border-bottom: 1px solid var(--border);
387
+ white-space: nowrap;
388
+ }
389
+
390
+ .data-table td {
391
+ padding: 10px 10px;
392
+ border-bottom: 1px solid var(--border);
393
+ color: var(--text-secondary);
394
+ vertical-align: middle;
395
+ }
396
+
397
+ .data-table tr:last-child td { border-bottom: none; }
398
+ .data-table tr:hover td { background: var(--row-hover); }
@@ -38,13 +38,13 @@ function planRowHtml(plan) {
38
38
  const id = escAttr(plan.id);
39
39
  const intervalLabel = plan.interval ? `/ ${plan.interval}` : '';
40
40
  const price = plan.price_cents === 0 ? 'Free' : `${(plan.price_cents / 100).toFixed(2)} ${(plan.currency || 'usd').toUpperCase()} ${intervalLabel}`;
41
- const status = plan.is_active ? '<span style="color:var(--success)">Active</span>' : '<span style="color:var(--muted)">Inactive</span>';
41
+ const status = plan.is_active ? '<span style="color:var(--success)">Active</span>' : '<span style="color:var(--text-muted)">Inactive</span>';
42
42
  return `
43
43
  <tr data-plan-id="${id}">
44
- <td><strong>${esc(plan.name)}</strong><br><small style="color:var(--muted)">${esc(plan.id)}</small></td>
44
+ <td><strong>${esc(plan.name)}</strong><br><small style="color:var(--text-muted)">${esc(plan.id)}</small></td>
45
45
  <td>${esc(price)}</td>
46
- <td>${plan.token_limit_4h != null ? fmtTokens(plan.token_limit_4h) : '<span style="color:var(--muted)">Default</span>'}</td>
47
- <td>${plan.token_limit_weekly != null ? fmtTokens(plan.token_limit_weekly) : '<span style="color:var(--muted)">Default</span>'}</td>
46
+ <td>${plan.token_limit_4h != null ? fmtTokens(plan.token_limit_4h) : '<span style="color:var(--text-muted)">Default</span>'}</td>
47
+ <td>${plan.token_limit_weekly != null ? fmtTokens(plan.token_limit_weekly) : '<span style="color:var(--text-muted)">Default</span>'}</td>
48
48
  <td><code style="font-size:11px">${esc(plan.stripe_price_id || '—')}</code></td>
49
49
  <td>${status}</td>
50
50
  <td>
@@ -83,7 +83,7 @@ function billingOpenPlanModal(plan) {
83
83
  const title = isNew ? 'New Plan' : `Edit Plan: ${plan.name}`;
84
84
  const html = `
85
85
  <div id="billing-plan-modal" style="position:fixed;inset:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:1000">
86
- <div style="background:var(--card-bg);border-radius:12px;padding:28px;width:540px;max-height:90vh;overflow-y:auto;box-shadow:0 8px 32px rgba(0,0,0,0.3)">
86
+ <div style="background:var(--bg-card);border-radius:12px;padding:28px;width:540px;max-height:90vh;overflow-y:auto;box-shadow:0 8px 32px rgba(0,0,0,0.3)">
87
87
  <h2 style="margin:0 0 20px">${esc(title)}</h2>
88
88
  <form id="billing-plan-form" onsubmit="billingPlanFormSubmit(event)">
89
89
  <input type="hidden" id="bp-id" value="${esc(plan?.id || '')}">
@@ -234,15 +234,15 @@ function renderSubsTable(rows, total) {
234
234
  const hasPrev = _billingSubsOffset > 0;
235
235
  const hasNext = _billingSubsOffset + BILLING_SUBS_LIMIT < total;
236
236
  pag.innerHTML = `
237
- <span style="color:var(--muted);font-size:13px">${total} total</span>
237
+ <span style="color:var(--text-muted);font-size:13px">${total} total</span>
238
238
  ${hasPrev ? `<button class="btn btn-sm" onclick="_billingSubsOffset-=${BILLING_SUBS_LIMIT};loadBillingSubscriptions()">← Prev</button>` : ''}
239
239
  ${hasNext ? `<button class="btn btn-sm" onclick="_billingSubsOffset+=${BILLING_SUBS_LIMIT};loadBillingSubscriptions()">Next →</button>` : ''}`;
240
240
  }
241
241
 
242
242
  function subRowHtml(sub) {
243
243
  const userLabel = esc(sub.display_name || sub.username || String(sub.user_id));
244
- const email = sub.email ? `<br><small style="color:var(--muted)">${esc(sub.email)}</small>` : '';
245
- const statusColor = { active: 'var(--success)', trialing: 'var(--warning, #f59e0b)', past_due: 'var(--error)', canceled: 'var(--muted)' }[sub.status] || 'var(--muted)';
244
+ const email = sub.email ? `<br><small style="color:var(--text-muted)">${esc(sub.email)}</small>` : '';
245
+ const statusColor = { active: 'var(--success)', trialing: 'var(--warning)', past_due: 'var(--danger)', canceled: 'var(--text-muted)' }[sub.status] || 'var(--text-muted)';
246
246
  const periodEnd = sub.current_period_end ? sub.current_period_end.slice(0, 10) : '—';
247
247
  return `
248
248
  <tr>
@@ -871,7 +871,7 @@
871
871
 
872
872
  <button class="nav-item" data-page="issues" onclick="showPage('issues',this)">
873
873
  <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
874
- <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
874
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
875
875
  </svg>
876
876
  Issues
877
877
  <span class="nav-badge" id="issues-badge" hidden>0</span>
@@ -1198,8 +1198,13 @@
1198
1198
  </section>
1199
1199
 
1200
1200
  <section id="page-billing" class="page" aria-label="Billing">
1201
- <div class="page-header"><h1>Billing</h1></div>
1202
- <div class="cards">
1201
+ <div class="page-header">
1202
+ <div>
1203
+ <h1 class="page-title">Billing</h1>
1204
+ <p class="page-subtitle">Subscription plans and user billing management</p>
1205
+ </div>
1206
+ </div>
1207
+ <div class="content">
1203
1208
  <div class="card">
1204
1209
  <div class="card-title">Subscription Plans</div>
1205
1210
  <div style="margin-bottom:12px">
@@ -1 +1 @@
1
- dc90ac758721f68d16ff899fbce82971
1
+ 4d0927fbddb563e4c34344ab07ec61ec
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"77e2e94772b6eb43759e34ed1ad7da4674e19c
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "3960040413" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "3425829902" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -135597,7 +135597,7 @@ r===$&&A.b()
135597
135597
  p.push(A.jV(q,A.jd(!1,new A.a_(B.uM,A.db(new A.cC(B.jA,new A.a89(r,q),q),q,q),q),!1,B.I,!0),q,q,0,0,0,q))}r=!1
135598
135598
  if(!s.ay)if(!s.ch){r=s.e
135599
135599
  r===$&&A.b()
135600
- r=B.b.u("mqjy0ig5-61a79c0").length!==0&&r.b}if(r){r=s.d
135600
+ r=B.b.u("mqkodqpj-f9ba2c1").length!==0&&r.b}if(r){r=s.d
135601
135601
  r===$&&A.b()
135602
135602
  r=r.aP&&!r.ai?84:0
135603
135603
  s=s.e
@@ -141319,7 +141319,7 @@ $S:0}
141319
141319
  A.a_p.prototype={}
141320
141320
  A.T7.prototype={
141321
141321
  nd(a){var s=this
141322
- if(B.b.u("mqjy0ig5-61a79c0").length===0||s.a!=null)return
141322
+ if(B.b.u("mqkodqpj-f9ba2c1").length===0||s.a!=null)return
141323
141323
  s.B_()
141324
141324
  s.a=A.ot(B.S2,new A.bde(s))},
141325
141325
  B_(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
@@ -141337,7 +141337,7 @@ if(!t.f.b(k)){s=1
141337
141337
  break}i=J.a3(k,"buildId")
141338
141338
  h=i==null?null:B.b.u(J.p(i))
141339
141339
  j=h==null?"":h
141340
- if(J.bj(j)===0||J.e(j,"mqjy0ig5-61a79c0")){s=1
141340
+ if(J.bj(j)===0||J.e(j,"mqkodqpj-f9ba2c1")){s=1
141341
141341
  break}n.b=!0
141342
141342
  n.F()
141343
141343
  p=2
@@ -141354,7 +141354,7 @@ case 2:return A.i(o.at(-1),r)}})
141354
141354
  return A.k($async$B_,r)},
141355
141355
  vI(){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
141356
141356
  var $async$vI=A.h(function(a2,a3){if(a2===1){o.push(a3)
141357
- s=p}for(;;)switch(s){case 0:if(B.b.u("mqjy0ig5-61a79c0").length===0||n.c){s=1
141357
+ s=p}for(;;)switch(s){case 0:if(B.b.u("mqkodqpj-f9ba2c1").length===0||n.c){s=1
141358
141358
  break}n.c=!0
141359
141359
  n.F()
141360
141360
  p=4
@@ -131,32 +131,50 @@ class RuntimeHttpClient {
131
131
  }
132
132
 
133
133
  async requestStream(method, pathname, stream, options = {}) {
134
- const response = await fetch(`${this.baseUrl}${pathname}`, {
135
- method,
136
- headers: {
137
- ...(options.contentType ? { 'content-type': options.contentType } : {}),
138
- ...(options.contentLength != null ? { 'content-length': String(options.contentLength) } : {}),
139
- ...(options.headers || {}),
140
- ...(this.token ? { authorization: `Bearer ${this.token}` } : {}),
141
- },
142
- body: stream,
143
- duplex: 'half',
144
- });
134
+ const retryCount = Math.max(0, Number(options.retryCount ?? 6));
135
+ const retryDelayMs = Math.max(100, Number(options.retryDelayMs ?? 1000));
136
+ let lastError = null;
145
137
 
146
- if (response.ok && typeof this.onActivity === 'function') {
147
- this.onActivity();
148
- }
138
+ for (let attempt = 0; attempt <= retryCount; attempt += 1) {
139
+ try {
140
+ const response = await fetch(`${this.baseUrl}${pathname}`, {
141
+ method,
142
+ headers: {
143
+ ...(options.contentType ? { 'content-type': options.contentType } : {}),
144
+ ...(options.contentLength != null ? { 'content-length': String(options.contentLength) } : {}),
145
+ ...(options.headers || {}),
146
+ ...(this.token ? { authorization: `Bearer ${this.token}` } : {}),
147
+ },
148
+ body: stream,
149
+ duplex: 'half',
150
+ });
149
151
 
150
- const contentType = response.headers.get('content-type') || '';
151
- const payload = contentType.includes('application/json')
152
- ? await response.json().catch(() => ({}))
153
- : { text: await response.text().catch(() => '') };
152
+ if (response.ok && typeof this.onActivity === 'function') {
153
+ this.onActivity();
154
+ }
155
+
156
+ const contentType = response.headers.get('content-type') || '';
157
+ const payload = contentType.includes('application/json')
158
+ ? await response.json().catch(() => ({}))
159
+ : { text: await response.text().catch(() => '') };
154
160
 
155
- if (!response.ok) {
156
- const errorMessage = payload?.error || payload?.text || `Runtime request failed: ${response.status}`;
157
- throw new Error(errorMessage);
161
+ if (!response.ok) {
162
+ const errorMessage = payload?.error || payload?.text || `Runtime request failed: ${response.status}`;
163
+ throw new Error(errorMessage);
164
+ }
165
+ return payload;
166
+ } catch (error) {
167
+ lastError = error;
168
+ const message = String(error?.message || error);
169
+ const retryable = /fetch failed|ECONNREFUSED|ECONNRESET|socket hang up|timed out/i.test(message);
170
+ if (!retryable || attempt === retryCount) {
171
+ throw error;
172
+ }
173
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
174
+ }
158
175
  }
159
- return payload;
176
+
177
+ throw lastError || new Error('Runtime request failed.');
160
178
  }
161
179
  }
162
180
 
@@ -141,11 +141,11 @@ function parseCronExpression(expression) {
141
141
  }
142
142
 
143
143
  function matchesCron(date, schedule) {
144
- const minute = date.getUTCMinutes();
145
- const hour = date.getUTCHours();
146
- const dayOfMonth = date.getUTCDate();
147
- const month = date.getUTCMonth() + 1;
148
- const dayOfWeek = date.getUTCDay();
144
+ const minute = date.getMinutes();
145
+ const hour = date.getHours();
146
+ const dayOfMonth = date.getDate();
147
+ const month = date.getMonth() + 1;
148
+ const dayOfWeek = date.getDay();
149
149
 
150
150
  if (!schedule.minute.values.has(minute)) return false;
151
151
  if (!schedule.hour.values.has(hour)) return false;