neoagent 1.1.2 → 1.2.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.
package/lib/manager.js CHANGED
@@ -406,10 +406,10 @@ function cmdUpdate() {
406
406
  logWarn('No git repo detected; attempting npm global update.');
407
407
  if (commandExists('npm')) {
408
408
  try {
409
- runOrThrow('npm', ['update', '-g', 'neoagent']);
409
+ runOrThrow('npm', ['install', '-g', 'neoagent@latest']);
410
410
  logOk('npm global update completed');
411
411
  } catch {
412
- logWarn('npm global update failed. Run: npm update -g neoagent');
412
+ logWarn('npm global update failed. Run: npm install -g neoagent@latest');
413
413
  }
414
414
  } else {
415
415
  logWarn('npm not found. Cannot perform global update.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",
package/server/index.js CHANGED
@@ -16,6 +16,7 @@ const { sanitizeError } = require('./utils/security');
16
16
  const { setupConsoleInterceptor } = require('./utils/logger');
17
17
  const { setupTelnyxWebhook } = require('./routes/telnyx');
18
18
  const { startServices } = require('./services/manager');
19
+ const packageJson = require('../package.json');
19
20
 
20
21
  const app = express();
21
22
  const httpServer = createServer(app);
@@ -53,7 +54,7 @@ app.use(helmet({
53
54
  contentSecurityPolicy: {
54
55
  directives: {
55
56
  defaultSrc: ["'self'"],
56
- scriptSrc: ["'self'", "'unsafe-inline'"],
57
+ scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net"],
57
58
  scriptSrcAttr: ["'unsafe-inline'"],
58
59
  styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
59
60
  imgSrc: ["'self'", "data:", "blob:", "https://api.qrserver.com"],
@@ -153,6 +154,26 @@ app.get('/api/health', requireAuth, (req, res) => {
153
154
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
154
155
  });
155
156
 
157
+ app.get('/api/version', requireAuth, (req, res) => {
158
+ let gitSha = null;
159
+ try {
160
+ const { execSync } = require('child_process');
161
+ gitSha = execSync('git rev-parse --short HEAD', {
162
+ cwd: path.join(__dirname, '..'),
163
+ encoding: 'utf8',
164
+ stdio: ['ignore', 'pipe', 'ignore']
165
+ }).trim();
166
+ } catch {
167
+ gitSha = process.env.GIT_SHA || null;
168
+ }
169
+
170
+ res.json({
171
+ name: packageJson.name,
172
+ version: packageJson.version,
173
+ gitSha
174
+ });
175
+ });
176
+
156
177
  // ── Service Initialization ──
157
178
  // Handled by services/manager.js
158
179
 
@@ -44,9 +44,22 @@ async function api(path, opts = {}) {
44
44
  ...opts,
45
45
  body: opts.body ? JSON.stringify(opts.body) : undefined,
46
46
  });
47
- const data = await res.json();
48
- if (!res.ok) throw new Error(data.error || "Request failed");
49
- return data;
47
+
48
+ const contentType = (res.headers.get("content-type") || "").toLowerCase();
49
+ let data = null;
50
+ if (contentType.includes("application/json")) {
51
+ data = await res.json();
52
+ } else {
53
+ const text = await res.text();
54
+ data = { error: text || `Request failed (${res.status})` };
55
+ }
56
+
57
+ if (!res.ok) {
58
+ const err = new Error(data?.error || `Request failed (${res.status})`);
59
+ err.status = res.status;
60
+ throw err;
61
+ }
62
+ return data || {};
50
63
  }
51
64
 
52
65
  function escapeHtml(str) {
@@ -1178,6 +1191,7 @@ if (copyLogsBtn) {
1178
1191
 
1179
1192
  let updateStatusPollTimer = null;
1180
1193
  let updateFinishNotifiedAt = null;
1194
+ let backendVersionLabel = null;
1181
1195
 
1182
1196
  function clearUpdatePoll() {
1183
1197
  if (updateStatusPollTimer) {
@@ -1217,7 +1231,9 @@ function renderUpdateStatus(status) {
1217
1231
 
1218
1232
  const before = status?.versionBefore || "—";
1219
1233
  const after = status?.versionAfter || "—";
1220
- $("#updateVersionMeta").textContent = `Version: ${before}${after !== "—" ? ` -> ${after}` : ""}`;
1234
+ const updateVersionLabel = `${before}${after !== "—" ? ` -> ${after}` : ""}`;
1235
+ const backendLabel = backendVersionLabel ? ` | Backend: ${backendVersionLabel}` : "";
1236
+ $("#updateVersionMeta").textContent = `Update Version: ${updateVersionLabel}${backendLabel}`;
1221
1237
 
1222
1238
  const changelog = $("#updateChangelog");
1223
1239
  changelog.innerHTML = "";
@@ -1265,7 +1281,17 @@ async function refreshUpdateStatus() {
1265
1281
  clearUpdatePoll();
1266
1282
  }
1267
1283
  } catch (err) {
1268
- // During restart window, polling can fail briefly; keep trying.
1284
+ // During restart window this can fail briefly; keep trying.
1285
+ // If endpoint is unavailable (older backend), stop polling to avoid console spam.
1286
+ if (err?.status === 404) {
1287
+ clearUpdatePoll();
1288
+ $("#updatePhaseLabel").textContent = "Update status unavailable on this server version.";
1289
+ $("#updatePercentLabel").textContent = "—";
1290
+ setUpdateBadgeState("idle");
1291
+ const btn = $("#updateAppBtn");
1292
+ if (btn) btn.disabled = false;
1293
+ return;
1294
+ }
1269
1295
  $("#updatePhaseLabel").textContent = "Reconnecting to server…";
1270
1296
  setUpdateBadgeState("running");
1271
1297
  }
@@ -1295,12 +1321,25 @@ function renderTokenUsageSummary(summary) {
1295
1321
 
1296
1322
  $("#settingsBtn").addEventListener("click", async () => {
1297
1323
  try {
1298
- const [meta, settings, tokenUsage] = await Promise.all([
1324
+ const [meta, settings] = await Promise.all([
1299
1325
  api("/settings/meta/models"),
1300
- api("/settings"),
1301
- api("/settings/token-usage/summary")
1326
+ api("/settings")
1302
1327
  ]);
1303
- renderTokenUsageSummary(tokenUsage);
1328
+
1329
+ try {
1330
+ const backendVersion = await api("/version");
1331
+ backendVersionLabel = `${backendVersion?.version || "unknown"}${backendVersion?.gitSha ? ` (${backendVersion.gitSha})` : ""}`;
1332
+ } catch {
1333
+ backendVersionLabel = "unavailable";
1334
+ }
1335
+
1336
+ try {
1337
+ const tokenUsage = await api("/settings/token-usage/summary");
1338
+ renderTokenUsageSummary(tokenUsage);
1339
+ } catch (err) {
1340
+ const tokenBox = $("#tokenUsageSummary");
1341
+ if (tokenBox) tokenBox.textContent = "Token usage unavailable on this server version.";
1342
+ }
1304
1343
 
1305
1344
  $("#settingHeartbeat").checked =
1306
1345
  settings.heartbeat_enabled === true ||
@@ -1373,6 +1412,7 @@ $("#settingsBtn").addEventListener("click", async () => {
1373
1412
  $("#settingHeadlessBrowser").checked = true; // default headless
1374
1413
  const tokenBox = $("#tokenUsageSummary");
1375
1414
  if (tokenBox) tokenBox.textContent = "Token usage unavailable.";
1415
+ backendVersionLabel = "unavailable";
1376
1416
  }
1377
1417
  await refreshUpdateStatus();
1378
1418
  ensureUpdatePolling(true);