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 +1 -1
- package/server/admin/admin.css +78 -0
- package/server/admin/billing.js +8 -8
- package/server/admin/index.html +8 -3
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +4 -4
- package/server/services/runtime/backends/local-vm.js +40 -22
- package/server/services/tasks/schedule_utils.js +5 -5
package/package.json
CHANGED
package/server/admin/admin.css
CHANGED
|
@@ -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); }
|
package/server/admin/billing.js
CHANGED
|
@@ -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
|
|
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
|
|
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>
|
package/server/admin/index.html
CHANGED
|
@@ -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="
|
|
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"
|
|
1202
|
-
|
|
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
|
-
|
|
1
|
+
4d0927fbddb563e4c34344ab07ec61ec
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"77e2e94772b6eb43759e34ed1ad7da4674e19c
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
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("
|
|
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("
|
|
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,"
|
|
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("
|
|
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
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
147
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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.
|
|
145
|
-
const hour = date.
|
|
146
|
-
const dayOfMonth = date.
|
|
147
|
-
const month = date.
|
|
148
|
-
const dayOfWeek = date.
|
|
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;
|